diff --git a/test/development/acceptance-app/ReactRefreshLogBox-builtins.test.ts b/test/development/acceptance-app/ReactRefreshLogBox-builtins.test.ts index 26c403611a4e1..936488394b58a 100644 --- a/test/development/acceptance-app/ReactRefreshLogBox-builtins.test.ts +++ b/test/development/acceptance-app/ReactRefreshLogBox-builtins.test.ts @@ -2,74 +2,78 @@ import { sandbox } from './helpers' import { createNext, FileRef } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' import path from 'path' +import { getSnapshotTestDescribe } from 'next-test-utils' // TODO-APP: Investigate snapshot mismatch -describe('ReactRefreshLogBox app', () => { - if (process.env.NEXT_TEST_REACT_VERSION === '^17') { - it('should skip for react v17', () => {}) - return - } - - let next: NextInstance - - beforeAll(async () => { - next = await createNext({ - files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')), - dependencies: { - react: 'latest', - 'react-dom': 'latest', - }, - skipStart: true, +for (const variant of ['default', 'turbo']) { + getSnapshotTestDescribe(variant)(`ReactRefreshLogBox app ${variant}`, () => { + if (process.env.NEXT_TEST_REACT_VERSION === '^17') { + it('should skip for react v17', () => {}) + return + } + + let next: NextInstance + + beforeAll(async () => { + next = await createNext({ + files: new FileRef( + path.join(__dirname, 'fixtures', 'default-template') + ), + dependencies: { + react: 'latest', + 'react-dom': 'latest', + }, + skipStart: true, + }) }) - }) - afterAll(() => next.destroy()) - - // Module trace is only available with webpack 5 - test('Node.js builtins', async () => { - const { session, cleanup } = await sandbox( - next, - new Map([ - [ - 'node_modules/my-package/index.js', - ` + afterAll(() => next.destroy()) + + // Module trace is only available with webpack 5 + test('Node.js builtins', async () => { + const { session, cleanup } = await sandbox( + next, + new Map([ + [ + 'node_modules/my-package/index.js', + ` const dns = require('dns') module.exports = dns `, - ], - [ - 'node_modules/my-package/package.json', - ` + ], + [ + 'node_modules/my-package/package.json', + ` { "name": "my-package", "version": "0.0.1" } `, - ], - ]) - ) + ], + ]) + ) - await session.patch( - 'index.js', - ` + await session.patch( + 'index.js', + ` import pkg from 'my-package' export default function Hello() { return (pkg ?

Package loaded

:

Package did not load

) } ` - ) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxSource()).toMatchSnapshot() + ) + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxSource()).toMatchSnapshot() - await cleanup() - }) + await cleanup() + }) - test('Module not found', async () => { - const { session, cleanup } = await sandbox(next) + test('Module not found', async () => { + const { session, cleanup } = await sandbox(next) - await session.patch( - 'index.js', - `import Comp from 'b' + await session.patch( + 'index.js', + `import Comp from 'b' export default function Oops() { return (
@@ -78,22 +82,22 @@ describe('ReactRefreshLogBox app', () => { ) } ` - ) + ) - expect(await session.hasRedbox(true)).toBe(true) + expect(await session.hasRedbox(true)).toBe(true) - const source = await session.getRedboxSource() - expect(source).toMatchSnapshot() + const source = await session.getRedboxSource() + expect(source).toMatchSnapshot() - await cleanup() - }) + await cleanup() + }) - test('Module not found empty import trace', async () => { - const { session, cleanup } = await sandbox(next) + test('Module not found empty import trace', async () => { + const { session, cleanup } = await sandbox(next) - await session.patch( - 'app/page.js', - `'use client' + await session.patch( + 'app/page.js', + `'use client' import Comp from 'b' export default function Oops() { return ( @@ -103,49 +107,50 @@ describe('ReactRefreshLogBox app', () => { ) } ` - ) + ) - expect(await session.hasRedbox(true)).toBe(true) + expect(await session.hasRedbox(true)).toBe(true) - const source = await session.getRedboxSource() - expect(source).toMatchSnapshot() + const source = await session.getRedboxSource() + expect(source).toMatchSnapshot() - await cleanup() - }) + await cleanup() + }) - test('Module not found missing global CSS', async () => { - const { session, cleanup } = await sandbox( - next, - new Map([ - [ - 'app/page.js', - `'use client' + test('Module not found missing global CSS', async () => { + const { session, cleanup } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + `'use client' import './non-existent.css' export default function Page(props) { return

index page

} `, - ], - ]) - ) - expect(await session.hasRedbox(true)).toBe(true) + ], + ]) + ) + expect(await session.hasRedbox(true)).toBe(true) - const source = await session.getRedboxSource() - expect(source).toMatchSnapshot() + const source = await session.getRedboxSource() + expect(source).toMatchSnapshot() - await session.patch( - 'app/page.js', - `'use client' + await session.patch( + 'app/page.js', + `'use client' export default function Page(props) { return

index page

} ` - ) - expect(await session.hasRedbox(false)).toBe(false) - expect( - await session.evaluate(() => document.documentElement.innerHTML) - ).toContain('index page') + ) + expect(await session.hasRedbox(false)).toBe(false) + expect( + await session.evaluate(() => document.documentElement.innerHTML) + ).toContain('index page') - await cleanup() + await cleanup() + }) }) -}) +} diff --git a/test/development/acceptance-app/ReactRefreshLogBox.test.ts b/test/development/acceptance-app/ReactRefreshLogBox.test.ts index b11f6b5fa285a..466191000a5b0 100644 --- a/test/development/acceptance-app/ReactRefreshLogBox.test.ts +++ b/test/development/acceptance-app/ReactRefreshLogBox.test.ts @@ -2,35 +2,38 @@ import { sandbox } from './helpers' import { createNext, FileRef } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' -import { check } from 'next-test-utils' +import { check, getSnapshotTestDescribe } from 'next-test-utils' import path from 'path' -describe('ReactRefreshLogBox app', () => { - if (process.env.NEXT_TEST_REACT_VERSION === '^17') { - it('should skip for react v17', () => {}) - return - } - - let next: NextInstance - - beforeAll(async () => { - next = await createNext({ - files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')), - dependencies: { - react: 'latest', - 'react-dom': 'latest', - }, - skipStart: true, +for (const variant of ['default', 'turbo']) { + getSnapshotTestDescribe(variant)(`ReactRefreshLogBox app ${variant}`, () => { + if (process.env.NEXT_TEST_REACT_VERSION === '^17') { + it('should skip for react v17', () => {}) + return + } + + let next: NextInstance + + beforeAll(async () => { + next = await createNext({ + files: new FileRef( + path.join(__dirname, 'fixtures', 'default-template') + ), + dependencies: { + react: 'latest', + 'react-dom': 'latest', + }, + skipStart: true, + }) }) - }) - afterAll(() => next.destroy()) + afterAll(() => next.destroy()) - test('should strip whitespace correctly with newline', async () => { - const { session, cleanup } = await sandbox(next) + test('should strip whitespace correctly with newline', async () => { + const { session, cleanup } = await sandbox(next) - await session.patch( - 'index.js', - ` + await session.patch( + 'index.js', + ` export default function Page() { return ( <> @@ -46,21 +49,21 @@ describe('ReactRefreshLogBox app', () => { ) } ` - ) - await session.evaluate(() => document.querySelector('a').click()) + ) + await session.evaluate(() => document.querySelector('a').click()) - await session.waitForAndOpenRuntimeError() - expect(await session.getRedboxSource()).toMatchSnapshot() + await session.waitForAndOpenRuntimeError() + expect(await session.getRedboxSource()).toMatchSnapshot() - await cleanup() - }) + await cleanup() + }) - test('logbox: can recover from a syntax error without losing state', async () => { - const { session, cleanup } = await sandbox(next) + test('logbox: can recover from a syntax error without losing state', async () => { + const { session, cleanup } = await sandbox(next) - await session.patch( - 'index.js', - ` + await session.patch( + 'index.js', + ` import { useCallback, useState } from 'react' export default function Index() { @@ -74,21 +77,21 @@ describe('ReactRefreshLogBox app', () => { ) } ` - ) + ) - await session.evaluate(() => document.querySelector('button').click()) - expect( - await session.evaluate(() => document.querySelector('p').textContent) - ).toBe('1') + await session.evaluate(() => document.querySelector('button').click()) + expect( + await session.evaluate(() => document.querySelector('p').textContent) + ).toBe('1') - await session.patch('index.js', `export default () =>
{ ) } ` - ) + ) - await check( - () => session.evaluate(() => document.querySelector('p').textContent), - /Count: 1/ - ) + await check( + () => session.evaluate(() => document.querySelector('p').textContent), + /Count: 1/ + ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox()).toBe(false) - await cleanup() - }) + await cleanup() + }) - test('logbox: can recover from a event handler error', async () => { - const { session, cleanup } = await sandbox(next) + test('logbox: can recover from a event handler error', async () => { + const { session, cleanup } = await sandbox(next) - await session.patch( - 'index.js', - ` + await session.patch( + 'index.js', + ` import { useCallback, useState } from 'react' export default function Index() { @@ -136,26 +139,26 @@ describe('ReactRefreshLogBox app', () => { ) } ` - ) - - expect( - await session.evaluate(() => document.querySelector('p').textContent) - ).toBe('0') - await session.evaluate(() => document.querySelector('button').click()) - expect( - await session.evaluate(() => document.querySelector('p').textContent) - ).toBe('1') - - await session.waitForAndOpenRuntimeError() - if (process.platform === 'win32') { - expect(await session.getRedboxSource()).toMatchSnapshot() - } else { - expect(await session.getRedboxSource()).toMatchSnapshot() - } + ) - await session.patch( - 'index.js', - ` + expect( + await session.evaluate(() => document.querySelector('p').textContent) + ).toBe('0') + await session.evaluate(() => document.querySelector('button').click()) + expect( + await session.evaluate(() => document.querySelector('p').textContent) + ).toBe('1') + + await session.waitForAndOpenRuntimeError() + if (process.platform === 'win32') { + expect(await session.getRedboxSource()).toMatchSnapshot() + } else { + expect(await session.getRedboxSource()).toMatchSnapshot() + } + + await session.patch( + 'index.js', + ` import { useCallback, useState } from 'react' export default function Index() { @@ -169,40 +172,40 @@ describe('ReactRefreshLogBox app', () => { ) } ` - ) + ) - expect(await session.hasRedbox()).toBe(false) - expect(await session.hasErrorToast()).toBe(false) + expect(await session.hasRedbox()).toBe(false) + expect(await session.hasErrorToast()).toBe(false) - expect( - await session.evaluate(() => document.querySelector('p').textContent) - ).toBe('Count: 1') - await session.evaluate(() => document.querySelector('button').click()) - expect( - await session.evaluate(() => document.querySelector('p').textContent) - ).toBe('Count: 2') + expect( + await session.evaluate(() => document.querySelector('p').textContent) + ).toBe('Count: 1') + await session.evaluate(() => document.querySelector('button').click()) + expect( + await session.evaluate(() => document.querySelector('p').textContent) + ).toBe('Count: 2') - expect(await session.hasRedbox()).toBe(false) - expect(await session.hasErrorToast()).toBe(false) + expect(await session.hasRedbox()).toBe(false) + expect(await session.hasErrorToast()).toBe(false) - await cleanup() - }) + await cleanup() + }) - test('logbox: can recover from a component error', async () => { - const { session, cleanup } = await sandbox(next) + test('logbox: can recover from a component error', async () => { + const { session, cleanup } = await sandbox(next) - await session.write( - 'child.js', - ` + await session.write( + 'child.js', + ` export default function Child() { return

Hello

; } ` - ) + ) - await session.patch( - 'index.js', - ` + await session.patch( + 'index.js', + ` import Child from './child' export default function Index() { @@ -213,53 +216,53 @@ describe('ReactRefreshLogBox app', () => { ) } ` - ) + ) - expect( - await session.evaluate(() => document.querySelector('p').textContent) - ).toBe('Hello') + expect( + await session.evaluate(() => document.querySelector('p').textContent) + ).toBe('Hello') - await session.patch( - 'child.js', - ` + await session.patch( + 'child.js', + ` // hello export default function Child() { throw new Error('oops') } ` - ) + ) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxSource()).toMatchSnapshot() + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxSource()).toMatchSnapshot() - // TODO-APP: re-enable when error recovery doesn't reload the page. - /* const didNotReload = */ await session.patch( - 'child.js', - ` + // TODO-APP: re-enable when error recovery doesn't reload the page. + /* const didNotReload = */ await session.patch( + 'child.js', + ` export default function Child() { return

Hello

; } ` - ) + ) - // TODO-APP: re-enable when error recovery doesn't reload the page. - // expect(didNotReload).toBe(true) - expect(await session.hasRedbox()).toBe(false) - expect( - await session.evaluate(() => document.querySelector('p').textContent) - ).toBe('Hello') + // TODO-APP: re-enable when error recovery doesn't reload the page. + // expect(didNotReload).toBe(true) + expect(await session.hasRedbox()).toBe(false) + expect( + await session.evaluate(() => document.querySelector('p').textContent) + ).toBe('Hello') - await cleanup() - }) + await cleanup() + }) - // https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554137262 - test('render error not shown right after syntax error', async () => { - const { session, cleanup } = await sandbox(next) + // https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554137262 + test('render error not shown right after syntax error', async () => { + const { session, cleanup } = await sandbox(next) - // Starting here: - await session.patch( - 'index.js', - ` + // Starting here: + await session.patch( + 'index.js', + ` import * as React from 'react'; class ClassDefault extends React.Component { render() { @@ -269,16 +272,16 @@ describe('ReactRefreshLogBox app', () => { export default ClassDefault; ` - ) + ) - expect( - await session.evaluate(() => document.querySelector('h1').textContent) - ).toBe('Default Export') + expect( + await session.evaluate(() => document.querySelector('h1').textContent) + ).toBe('Default Export') - // Break it with a syntax error: - await session.patch( - 'index.js', - ` + // Break it with a syntax error: + await session.patch( + 'index.js', + ` import * as React from 'react'; class ClassDefault extends React.Component { @@ -289,13 +292,13 @@ describe('ReactRefreshLogBox app', () => { export default ClassDefault; ` - ) - expect(await session.hasRedbox(true)).toBe(true) + ) + expect(await session.hasRedbox(true)).toBe(true) - // Now change the code to introduce a runtime error without fixing the syntax error: - await session.patch( - 'index.js', - ` + // Now change the code to introduce a runtime error without fixing the syntax error: + await session.patch( + 'index.js', + ` import * as React from 'react'; class ClassDefault extends React.Component { @@ -307,13 +310,13 @@ describe('ReactRefreshLogBox app', () => { export default ClassDefault; ` - ) - expect(await session.hasRedbox(true)).toBe(true) + ) + expect(await session.hasRedbox(true)).toBe(true) - // Now fix the syntax error: - await session.patch( - 'index.js', - ` + // Now fix the syntax error: + await session.patch( + 'index.js', + ` import * as React from 'react'; class ClassDefault extends React.Component { @@ -325,28 +328,28 @@ describe('ReactRefreshLogBox app', () => { export default ClassDefault; ` - ) - expect(await session.hasRedbox(true)).toBe(true) + ) + expect(await session.hasRedbox(true)).toBe(true) - await check(async () => { - const source = await session.getRedboxSource() - return source?.length > 1 ? 'success' : source - }, 'success') + await check(async () => { + const source = await session.getRedboxSource() + return source?.length > 1 ? 'success' : source + }, 'success') - expect(await session.getRedboxSource()).toMatchSnapshot() + expect(await session.getRedboxSource()).toMatchSnapshot() - await cleanup() - }) + await cleanup() + }) - // https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554137807 - test('module init error not shown', async () => { - // Start here: - const { session, cleanup } = await sandbox(next) + // https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554137807 + test('module init error not shown', async () => { + // Start here: + const { session, cleanup } = await sandbox(next) - // We start here. - await session.patch( - 'index.js', - ` + // We start here. + await session.patch( + 'index.js', + ` import * as React from 'react'; class ClassDefault extends React.Component { render() { @@ -355,16 +358,16 @@ describe('ReactRefreshLogBox app', () => { } export default ClassDefault; ` - ) + ) - expect( - await session.evaluate(() => document.querySelector('h1').textContent) - ).toBe('Default Export') + expect( + await session.evaluate(() => document.querySelector('h1').textContent) + ).toBe('Default Export') - // Add a throw in module init phase: - await session.patch( - 'index.js', - ` + // Add a throw in module init phase: + await session.patch( + 'index.js', + ` // top offset for snapshot import * as React from 'react'; throw new Error('no') @@ -375,26 +378,26 @@ describe('ReactRefreshLogBox app', () => { } export default ClassDefault; ` - ) + ) - expect(await session.hasRedbox(true)).toBe(true) - if (process.platform === 'win32') { - expect(await session.getRedboxSource()).toMatchSnapshot() - } else { - expect(await session.getRedboxSource()).toMatchSnapshot() - } + expect(await session.hasRedbox(true)).toBe(true) + if (process.platform === 'win32') { + expect(await session.getRedboxSource()).toMatchSnapshot() + } else { + expect(await session.getRedboxSource()).toMatchSnapshot() + } - await cleanup() - }) + await cleanup() + }) - // https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554144016 - test('stuck error', async () => { - const { session, cleanup } = await sandbox(next) + // https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554144016 + test('stuck error', async () => { + const { session, cleanup } = await sandbox(next) - // We start here. - await session.patch( - 'index.js', - ` + // We start here. + await session.patch( + 'index.js', + ` import * as React from 'react'; function FunctionDefault() { @@ -403,23 +406,23 @@ describe('ReactRefreshLogBox app', () => { export default FunctionDefault; ` - ) + ) - // We add a new file. Let's call it Foo.js. - await session.write( - 'Foo.js', - ` + // We add a new file. Let's call it Foo.js. + await session.write( + 'Foo.js', + ` // intentionally skips export export default function Foo() { return React.createElement('h1', null, 'Foo'); } ` - ) + ) - // We edit our first file to use it. - await session.patch( - 'index.js', - ` + // We edit our first file to use it. + await session.patch( + 'index.js', + ` import * as React from 'react'; import Foo from './Foo'; function FunctionDefault() { @@ -427,48 +430,48 @@ describe('ReactRefreshLogBox app', () => { } export default FunctionDefault; ` - ) + ) - // We get an error because Foo didn't import React. Fair. - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxSource()).toMatchSnapshot() + // We get an error because Foo didn't import React. Fair. + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxSource()).toMatchSnapshot() - // Let's add that to Foo. - await session.patch( - 'Foo.js', - ` + // Let's add that to Foo. + await session.patch( + 'Foo.js', + ` import * as React from 'react'; export default function Foo() { return React.createElement('h1', null, 'Foo'); } ` - ) + ) - // Expected: this fixes the problem - expect(await session.hasRedbox()).toBe(false) + // Expected: this fixes the problem + expect(await session.hasRedbox()).toBe(false) - await cleanup() - }) + await cleanup() + }) - // https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554150098 - test('syntax > runtime error', async () => { - const { session, cleanup } = await sandbox(next) + // https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554150098 + test('syntax > runtime error', async () => { + const { session, cleanup } = await sandbox(next) - // Start here. - await session.patch( - 'index.js', - ` + // Start here. + await session.patch( + 'index.js', + ` import * as React from 'react'; export default function FunctionNamed() { return
} ` - ) - // TODO: this acts weird without above step - await session.patch( - 'index.js', - ` + ) + // TODO: this acts weird without above step + await session.patch( + 'index.js', + ` import * as React from 'react'; let i = 0 setInterval(() => { @@ -479,20 +482,20 @@ describe('ReactRefreshLogBox app', () => { return
} ` - ) + ) - await new Promise((resolve) => setTimeout(resolve, 1000)) - await session.waitForAndOpenRuntimeError() - if (process.platform === 'win32') { - expect(await session.getRedboxSource()).toMatchSnapshot() - } else { - expect(await session.getRedboxSource()).toMatchSnapshot() - } + await new Promise((resolve) => setTimeout(resolve, 1000)) + await session.waitForAndOpenRuntimeError() + if (process.platform === 'win32') { + expect(await session.getRedboxSource()).toMatchSnapshot() + } else { + expect(await session.getRedboxSource()).toMatchSnapshot() + } - // Make a syntax error. - await session.patch( - 'index.js', - ` + // Make a syntax error. + await session.patch( + 'index.js', + ` import * as React from 'react'; let i = 0 setInterval(() => { @@ -500,35 +503,35 @@ describe('ReactRefreshLogBox app', () => { throw Error('no ' + i) }, 1000) export default function FunctionNamed() {` - ) + ) - await new Promise((resolve) => setTimeout(resolve, 1000)) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxSource()).toMatchSnapshot() + await new Promise((resolve) => setTimeout(resolve, 1000)) + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxSource()).toMatchSnapshot() - // Test that runtime error does not take over: - await new Promise((resolve) => setTimeout(resolve, 2000)) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxSource()).toMatchSnapshot() + // Test that runtime error does not take over: + await new Promise((resolve) => setTimeout(resolve, 2000)) + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxSource()).toMatchSnapshot() - await cleanup() - }) + await cleanup() + }) - // https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554152127 - test('boundaries', async () => { - const { session, cleanup } = await sandbox(next) + // https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554152127 + test('boundaries', async () => { + const { session, cleanup } = await sandbox(next) - await session.write( - 'FunctionDefault.js', - ` + await session.write( + 'FunctionDefault.js', + ` export default function FunctionDefault() { return

hello

} ` - ) - await session.patch( - 'index.js', - ` + ) + await session.patch( + 'index.js', + ` import FunctionDefault from './FunctionDefault.js' import * as React from 'react' class ErrorBoundary extends React.Component { @@ -558,58 +561,58 @@ describe('ReactRefreshLogBox app', () => { } export default App; ` - ) + ) - expect( - await session.evaluate(() => document.querySelector('h2').textContent) - ).toBe('hello') + expect( + await session.evaluate(() => document.querySelector('h2').textContent) + ).toBe('hello') - await session.write( - 'FunctionDefault.js', - `export default function FunctionDefault() { throw new Error('no'); }` - ) + await session.write( + 'FunctionDefault.js', + `export default function FunctionDefault() { throw new Error('no'); }` + ) - await session.waitForAndOpenRuntimeError() - expect(await session.getRedboxSource()).toMatchSnapshot() - expect( - await session.evaluate(() => document.querySelector('h2').textContent) - ).toBe('error') + await session.waitForAndOpenRuntimeError() + expect(await session.getRedboxSource()).toMatchSnapshot() + expect( + await session.evaluate(() => document.querySelector('h2').textContent) + ).toBe('error') - await cleanup() - }) + await cleanup() + }) - // TODO: investigate why this fails when running outside of the Next.js - // monorepo e.g. fails when using yarn create next-app - // https://github.com/vercel/next.js/pull/23203 - test.skip('internal package errors', async () => { - const { session, cleanup } = await sandbox(next) + // TODO: investigate why this fails when running outside of the Next.js + // monorepo e.g. fails when using yarn create next-app + // https://github.com/vercel/next.js/pull/23203 + test.skip('internal package errors', async () => { + const { session, cleanup } = await sandbox(next) - // Make a react build-time error. - await session.patch( - 'index.js', - ` + // Make a react build-time error. + await session.patch( + 'index.js', + ` export default function FunctionNamed() { return
{{}}
}` - ) + ) - expect(await session.hasRedbox(true)).toBe(true) - // We internally only check the script path, not including the line number - // and error message because the error comes from an external library. - // This test ensures that the errored script path is correctly resolved. - expect(await session.getRedboxSource()).toContain( - `../../../../packages/next/dist/pages/_document.js` - ) + expect(await session.hasRedbox(true)).toBe(true) + // We internally only check the script path, not including the line number + // and error message because the error comes from an external library. + // This test ensures that the errored script path is correctly resolved. + expect(await session.getRedboxSource()).toContain( + `../../../../packages/next/dist/pages/_document.js` + ) - await cleanup() - }) + await cleanup() + }) - test('unterminated JSX', async () => { - const { session, cleanup } = await sandbox(next) + test('unterminated JSX', async () => { + const { session, cleanup } = await sandbox(next) - await session.patch( - 'index.js', - ` + await session.patch( + 'index.js', + ` export default () => { return (
@@ -618,13 +621,13 @@ describe('ReactRefreshLogBox app', () => { ) } ` - ) + ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox()).toBe(false) - await session.patch( - 'index.js', - ` + await session.patch( + 'index.js', + ` export default () => { return (
@@ -633,32 +636,32 @@ describe('ReactRefreshLogBox app', () => { ) } ` - ) + ) - expect(await session.hasRedbox(true)).toBe(true) + expect(await session.hasRedbox(true)).toBe(true) - const source = await session.getRedboxSource() - expect(source).toMatchSnapshot() + const source = await session.getRedboxSource() + expect(source).toMatchSnapshot() - await cleanup() - }) + await cleanup() + }) - // Module trace is only available with webpack 5 - test('conversion to class component (1)', async () => { - const { session, cleanup } = await sandbox(next) + // Module trace is only available with webpack 5 + test('conversion to class component (1)', async () => { + const { session, cleanup } = await sandbox(next) - await session.write( - 'Child.js', - ` + await session.write( + 'Child.js', + ` export default function ClickCount() { return

hello

} ` - ) + ) - await session.patch( - 'index.js', - ` + await session.patch( + 'index.js', + ` import Child from './Child'; export default function Home() { @@ -669,16 +672,16 @@ describe('ReactRefreshLogBox app', () => { ) } ` - ) + ) - expect(await session.hasRedbox()).toBe(false) - expect( - await session.evaluate(() => document.querySelector('p').textContent) - ).toBe('hello') + expect(await session.hasRedbox()).toBe(false) + expect( + await session.evaluate(() => document.querySelector('p').textContent) + ).toBe('hello') - await session.patch( - 'Child.js', - ` + await session.patch( + 'Child.js', + ` import { Component } from 'react'; export default class ClickCount extends Component { render() { @@ -686,14 +689,14 @@ describe('ReactRefreshLogBox app', () => { } } ` - ) + ) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxSource()).toMatchSnapshot() + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxSource()).toMatchSnapshot() - await session.patch( - 'Child.js', - ` + await session.patch( + 'Child.js', + ` import { Component } from 'react'; export default class ClickCount extends Component { render() { @@ -701,23 +704,23 @@ describe('ReactRefreshLogBox app', () => { } } ` - ) + ) - expect(await session.hasRedbox()).toBe(false) - expect( - await session.evaluate(() => document.querySelector('p').textContent) - ).toBe('hello new') + expect(await session.hasRedbox()).toBe(false) + expect( + await session.evaluate(() => document.querySelector('p').textContent) + ).toBe('hello new') - await cleanup() - }) + await cleanup() + }) - test('css syntax errors', async () => { - const { session, cleanup } = await sandbox(next) + test('css syntax errors', async () => { + const { session, cleanup } = await sandbox(next) - await session.write('index.module.css', `.button {}`) - await session.patch( - 'index.js', - ` + await session.write('index.module.css', `.button {}`) + await session.patch( + 'index.js', + ` import './index.module.css'; export default () => { return ( @@ -727,35 +730,35 @@ describe('ReactRefreshLogBox app', () => { ) } ` - ) - - expect(await session.hasRedbox()).toBe(false) - - // Syntax error - await session.patch('index.module.css', `.button {`) - expect(await session.hasRedbox(true)).toBe(true) - const source = await session.getRedboxSource() - expect(source).toMatch('./index.module.css:1:1') - expect(source).toMatch('Syntax error: ') - expect(source).toMatch('Unclosed block') - expect(source).toMatch('> 1 | .button {') - expect(source).toMatch(' | ^') - - // Not local error - await session.patch('index.module.css', `button {}`) - expect(await session.hasRedbox(true)).toBe(true) - const source2 = await session.getRedboxSource() - expect(source2).toMatchSnapshot() - - await cleanup() - }) + ) - test('logbox: anchors links in error messages', async () => { - const { session, cleanup } = await sandbox(next) + expect(await session.hasRedbox()).toBe(false) - await session.patch( - 'index.js', - ` + // Syntax error + await session.patch('index.module.css', `.button {`) + expect(await session.hasRedbox(true)).toBe(true) + const source = await session.getRedboxSource() + expect(source).toMatch('./index.module.css:1:1') + expect(source).toMatch('Syntax error: ') + expect(source).toMatch('Unclosed block') + expect(source).toMatch('> 1 | .button {') + expect(source).toMatch(' | ^') + + // Not local error + await session.patch('index.module.css', `button {}`) + expect(await session.hasRedbox(true)).toBe(true) + const source2 = await session.getRedboxSource() + expect(source2).toMatchSnapshot() + + await cleanup() + }) + + test('logbox: anchors links in error messages', async () => { + const { session, cleanup } = await sandbox(next) + + await session.patch( + 'index.js', + ` import { useCallback } from 'react' export default function Index() { @@ -769,38 +772,38 @@ describe('ReactRefreshLogBox app', () => { ) } ` - ) - - await session.evaluate(() => document.querySelector('button').click()) - await session.waitForAndOpenRuntimeError() - - const header = await session.getRedboxDescription() - expect(header).toMatchSnapshot() - expect( - await session.evaluate( - () => - document - .querySelector('body > nextjs-portal') - .shadowRoot.querySelectorAll('#nextjs__container_errors_desc a') - .length ) - ).toBe(1) - expect( - await session.evaluate( - () => - ( + + await session.evaluate(() => document.querySelector('button').click()) + await session.waitForAndOpenRuntimeError() + + const header = await session.getRedboxDescription() + expect(header).toMatchSnapshot() + expect( + await session.evaluate( + () => document .querySelector('body > nextjs-portal') - .shadowRoot.querySelector( - '#nextjs__container_errors_desc a:nth-of-type(1)' - ) as any - ).href - ) - ).toMatchSnapshot() - - await session.patch( - 'index.js', - ` + .shadowRoot.querySelectorAll('#nextjs__container_errors_desc a') + .length + ) + ).toBe(1) + expect( + await session.evaluate( + () => + ( + document + .querySelector('body > nextjs-portal') + .shadowRoot.querySelector( + '#nextjs__container_errors_desc a:nth-of-type(1)' + ) as any + ).href + ) + ).toMatchSnapshot() + + await session.patch( + 'index.js', + ` import { useCallback } from 'react' export default function Index() { @@ -814,38 +817,38 @@ describe('ReactRefreshLogBox app', () => { ) } ` - ) - - await session.evaluate(() => document.querySelector('button').click()) - await session.waitForAndOpenRuntimeError() - - const header2 = await session.getRedboxDescription() - expect(header2).toMatchSnapshot() - expect( - await session.evaluate( - () => - document - .querySelector('body > nextjs-portal') - .shadowRoot.querySelectorAll('#nextjs__container_errors_desc a') - .length ) - ).toBe(1) - expect( - await session.evaluate( - () => - ( + + await session.evaluate(() => document.querySelector('button').click()) + await session.waitForAndOpenRuntimeError() + + const header2 = await session.getRedboxDescription() + expect(header2).toMatchSnapshot() + expect( + await session.evaluate( + () => document .querySelector('body > nextjs-portal') - .shadowRoot.querySelector( - '#nextjs__container_errors_desc a:nth-of-type(1)' - ) as any - ).href - ) - ).toMatchSnapshot() - - await session.patch( - 'index.js', - ` + .shadowRoot.querySelectorAll('#nextjs__container_errors_desc a') + .length + ) + ).toBe(1) + expect( + await session.evaluate( + () => + ( + document + .querySelector('body > nextjs-portal') + .shadowRoot.querySelector( + '#nextjs__container_errors_desc a:nth-of-type(1)' + ) as any + ).href + ) + ).toMatchSnapshot() + + await session.patch( + 'index.js', + ` import { useCallback } from 'react' export default function Index() { @@ -859,38 +862,38 @@ describe('ReactRefreshLogBox app', () => { ) } ` - ) - - await session.evaluate(() => document.querySelector('button').click()) - await session.waitForAndOpenRuntimeError() - - const header3 = await session.getRedboxDescription() - expect(header3).toMatchSnapshot() - expect( - await session.evaluate( - () => - document - .querySelector('body > nextjs-portal') - .shadowRoot.querySelectorAll('#nextjs__container_errors_desc a') - .length ) - ).toBe(1) - expect( - await session.evaluate( - () => - ( + + await session.evaluate(() => document.querySelector('button').click()) + await session.waitForAndOpenRuntimeError() + + const header3 = await session.getRedboxDescription() + expect(header3).toMatchSnapshot() + expect( + await session.evaluate( + () => document .querySelector('body > nextjs-portal') - .shadowRoot.querySelector( - '#nextjs__container_errors_desc a:nth-of-type(1)' - ) as any - ).href - ) - ).toMatchSnapshot() - - await session.patch( - 'index.js', - ` + .shadowRoot.querySelectorAll('#nextjs__container_errors_desc a') + .length + ) + ).toBe(1) + expect( + await session.evaluate( + () => + ( + document + .querySelector('body > nextjs-portal') + .shadowRoot.querySelector( + '#nextjs__container_errors_desc a:nth-of-type(1)' + ) as any + ).href + ) + ).toMatchSnapshot() + + await session.patch( + 'index.js', + ` import { useCallback } from 'react' export default function Index() { @@ -904,59 +907,59 @@ describe('ReactRefreshLogBox app', () => { ) } ` - ) - - await session.evaluate(() => document.querySelector('button').click()) - await session.waitForAndOpenRuntimeError() - - const header4 = await session.getRedboxDescription() - expect(header4).toMatchInlineSnapshot( - `"Error: multiple http://nextjs.org links http://example.com"` - ) - expect( - await session.evaluate( - () => - document - .querySelector('body > nextjs-portal') - .shadowRoot.querySelectorAll('#nextjs__container_errors_desc a') - .length ) - ).toBe(2) - expect( - await session.evaluate( - () => - ( - document - .querySelector('body > nextjs-portal') - .shadowRoot.querySelector( - '#nextjs__container_errors_desc a:nth-of-type(1)' - ) as any - ).href + + await session.evaluate(() => document.querySelector('button').click()) + await session.waitForAndOpenRuntimeError() + + const header4 = await session.getRedboxDescription() + expect(header4).toMatchInlineSnapshot( + `"Error: multiple http://nextjs.org links http://example.com"` ) - ).toMatchSnapshot() - expect( - await session.evaluate( - () => - ( + expect( + await session.evaluate( + () => document .querySelector('body > nextjs-portal') - .shadowRoot.querySelector( - '#nextjs__container_errors_desc a:nth-of-type(2)' - ) as any - ).href - ) - ).toMatchSnapshot() - - await cleanup() - }) + .shadowRoot.querySelectorAll('#nextjs__container_errors_desc a') + .length + ) + ).toBe(2) + expect( + await session.evaluate( + () => + ( + document + .querySelector('body > nextjs-portal') + .shadowRoot.querySelector( + '#nextjs__container_errors_desc a:nth-of-type(1)' + ) as any + ).href + ) + ).toMatchSnapshot() + expect( + await session.evaluate( + () => + ( + document + .querySelector('body > nextjs-portal') + .shadowRoot.querySelector( + '#nextjs__container_errors_desc a:nth-of-type(2)' + ) as any + ).href + ) + ).toMatchSnapshot() + + await cleanup() + }) - // TODO-APP: Catch errors that happen before useEffect - test.skip('non-Error errors are handled properly', async () => { - const { session, cleanup } = await sandbox(next) + // TODO-APP: Catch errors that happen before useEffect + test.skip('non-Error errors are handled properly', async () => { + const { session, cleanup } = await sandbox(next) - await session.patch( - 'index.js', - ` + await session.patch( + 'index.js', + ` export default () => { throw {'a': 1, 'b': 'x'}; return ( @@ -964,28 +967,28 @@ describe('ReactRefreshLogBox app', () => { ) } ` - ) + ) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxDescription()).toMatchInlineSnapshot( - `"Error: {\\"a\\":1,\\"b\\":\\"x\\"}"` - ) + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxDescription()).toMatchInlineSnapshot( + `"Error: {\\"a\\":1,\\"b\\":\\"x\\"}"` + ) - // fix previous error - await session.patch( - 'index.js', - ` + // fix previous error + await session.patch( + 'index.js', + ` export default () => { return (
hello
) } ` - ) - expect(await session.hasRedbox(false)).toBe(false) - await session.patch( - 'index.js', - ` + ) + expect(await session.hasRedbox(false)).toBe(false) + await session.patch( + 'index.js', + ` class Hello {} export default () => { @@ -995,27 +998,27 @@ describe('ReactRefreshLogBox app', () => { ) } ` - ) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxDescription()).toContain( - `Error: class Hello {` - ) - - // fix previous error - await session.patch( - 'index.js', - ` + ) + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxDescription()).toContain( + `Error: class Hello {` + ) + + // fix previous error + await session.patch( + 'index.js', + ` export default () => { return (
hello
) } ` - ) - expect(await session.hasRedbox(false)).toBe(false) - await session.patch( - 'index.js', - ` + ) + expect(await session.hasRedbox(false)).toBe(false) + await session.patch( + 'index.js', + ` export default () => { throw "string error" return ( @@ -1023,27 +1026,27 @@ describe('ReactRefreshLogBox app', () => { ) } ` - ) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxDescription()).toMatchInlineSnapshot( - `"Error: string error"` - ) - - // fix previous error - await session.patch( - 'index.js', - ` + ) + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxDescription()).toMatchInlineSnapshot( + `"Error: string error"` + ) + + // fix previous error + await session.patch( + 'index.js', + ` export default () => { return (
hello
) } ` - ) - expect(await session.hasRedbox(false)).toBe(false) - await session.patch( - 'index.js', - ` + ) + expect(await session.hasRedbox(false)).toBe(false) + await session.patch( + 'index.js', + ` export default () => { throw null return ( @@ -1051,21 +1054,21 @@ describe('ReactRefreshLogBox app', () => { ) } ` - ) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxDescription()).toContain( - `Error: A null error was thrown` - ) + ) + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxDescription()).toContain( + `Error: A null error was thrown` + ) - await cleanup() - }) + await cleanup() + }) - test('Should not show __webpack_exports__ when exporting anonymous arrow function', async () => { - const { session, cleanup } = await sandbox(next) + test('Should not show __webpack_exports__ when exporting anonymous arrow function', async () => { + const { session, cleanup } = await sandbox(next) - await session.patch( - 'index.js', - ` + await session.patch( + 'index.js', + ` export default () => { if (typeof window !== 'undefined') { throw new Error('test') @@ -1075,18 +1078,18 @@ describe('ReactRefreshLogBox app', () => { } ` - ) + ) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxSource()).toMatchSnapshot() + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxSource()).toMatchSnapshot() - await cleanup() - }) + await cleanup() + }) - test('Unhandled errors and rejections opens up in the minimized state', async () => { - const { session, browser, cleanup } = await sandbox(next) + test('Unhandled errors and rejections opens up in the minimized state', async () => { + const { session, browser, cleanup } = await sandbox(next) - const file = ` + const file = ` export default function Index() { // setTimeout(() => { @@ -1118,82 +1121,82 @@ describe('ReactRefreshLogBox app', () => { } ` - await session.patch('index.js', file) - - // Unhandled error and rejection in setTimeout - expect( - await browser.waitForElementByCss('.nextjs-toast-errors').text() - ).toBe('2 errors') - - // Unhandled error in event handler - await browser.elementById('unhandled-error').click() - await check( - () => browser.elementByCss('.nextjs-toast-errors').text(), - /3 errors/ - ) - - // Unhandled rejection in event handler - await browser.elementById('unhandled-rejection').click() - await check( - () => browser.elementByCss('.nextjs-toast-errors').text(), - /4 errors/ - ) - expect(await session.hasRedbox()).toBe(false) - - // Add Component error - await session.patch( - 'index.js', - file.replace( - '//', - "if (typeof window !== 'undefined') throw new Error('Component error')" + await session.patch('index.js', file) + + // Unhandled error and rejection in setTimeout + expect( + await browser.waitForElementByCss('.nextjs-toast-errors').text() + ).toBe('2 errors') + + // Unhandled error in event handler + await browser.elementById('unhandled-error').click() + await check( + () => browser.elementByCss('.nextjs-toast-errors').text(), + /3 errors/ + ) + + // Unhandled rejection in event handler + await browser.elementById('unhandled-rejection').click() + await check( + () => browser.elementByCss('.nextjs-toast-errors').text(), + /4 errors/ + ) + expect(await session.hasRedbox()).toBe(false) + + // Add Component error + await session.patch( + 'index.js', + file.replace( + '//', + "if (typeof window !== 'undefined') throw new Error('Component error')" + ) ) - ) - // Render error should "win" and show up in fullscreen - expect(await session.hasRedbox(true)).toBe(true) + // Render error should "win" and show up in fullscreen + expect(await session.hasRedbox(true)).toBe(true) - await cleanup() - }) + await cleanup() + }) - test('Call stack count is correct for server error', async () => { - const { session, browser, cleanup } = await sandbox( - next, - new Map([ - [ - 'app/page.js', - ` + test('Call stack count is correct for server error', async () => { + const { session, browser, cleanup } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` export default function Page() { throw new Error('Server error') } `, - ], - ]) - ) + ], + ]) + ) - expect(await session.hasRedbox(true)).toBe(true) + expect(await session.hasRedbox(true)).toBe(true) - // Open full Call Stack - await browser - .elementByCss('[data-nextjs-data-runtime-error-collapsed-action]') - .click() - const callStackCount = ( - await browser.elementsByCss('[data-nextjs-call-stack-frame]') - ).length + // Open full Call Stack + await browser + .elementByCss('[data-nextjs-data-runtime-error-collapsed-action]') + .click() + const callStackCount = ( + await browser.elementsByCss('[data-nextjs-call-stack-frame]') + ).length - // Expect more than the default amount of frames - // The default stackTraceLimit results in max 9 [data-nextjs-call-stack-frame] elements - expect(callStackCount).toBeGreaterThan(9) + // Expect more than the default amount of frames + // The default stackTraceLimit results in max 9 [data-nextjs-call-stack-frame] elements + expect(callStackCount).toBeGreaterThan(9) - await cleanup() - }) + await cleanup() + }) - test('Call stack count is correct for client error', async () => { - const { session, browser, cleanup } = await sandbox( - next, - new Map([ - [ - 'app/page.js', - ` + test('Call stack count is correct for client error', async () => { + const { session, browser, cleanup } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + ` 'use client' export default function Page() { if (typeof window !== 'undefined') { @@ -1202,72 +1205,73 @@ describe('ReactRefreshLogBox app', () => { return null } `, - ], - ]) - ) + ], + ]) + ) - expect(await session.hasRedbox(true)).toBe(true) + expect(await session.hasRedbox(true)).toBe(true) - // Open full Call Stack - await browser - .elementByCss('[data-nextjs-data-runtime-error-collapsed-action]') - .click() - const callStackCount = ( - await browser.elementsByCss('[data-nextjs-call-stack-frame]') - ).length + // Open full Call Stack + await browser + .elementByCss('[data-nextjs-data-runtime-error-collapsed-action]') + .click() + const callStackCount = ( + await browser.elementsByCss('[data-nextjs-call-stack-frame]') + ).length - // Expect more than the default amount of frames - // The default stackTraceLimit results in max 9 [data-nextjs-call-stack-frame] elements - expect(callStackCount).toBeGreaterThan(9) + // Expect more than the default amount of frames + // The default stackTraceLimit results in max 9 [data-nextjs-call-stack-frame] elements + expect(callStackCount).toBeGreaterThan(9) - await cleanup() - }) + await cleanup() + }) - test('Server component errors should open up in fullscreen', async () => { - const { session, browser, cleanup } = await sandbox( - next, - new Map([ - // Start with error - [ - 'app/page.js', - ` + test('Server component errors should open up in fullscreen', async () => { + const { session, browser, cleanup } = await sandbox( + next, + new Map([ + // Start with error + [ + 'app/page.js', + ` export default function Page() { throw new Error('Server component error') return

Hello world

} `, - ], - ]) - ) - expect(await session.hasRedbox(true)).toBe(true) - - // Remove error - await session.patch( - 'app/page.js', - ` + ], + ]) + ) + expect(await session.hasRedbox(true)).toBe(true) + + // Remove error + await session.patch( + 'app/page.js', + ` export default function Page() { return

Hello world

} ` - ) - expect(await browser.waitForElementByCss('#text').text()).toBe( - 'Hello world' - ) - expect(await session.hasRedbox()).toBe(false) - - // Re-add error - await session.patch( - 'app/page.js', - ` + ) + expect(await browser.waitForElementByCss('#text').text()).toBe( + 'Hello world' + ) + expect(await session.hasRedbox()).toBe(false) + + // Re-add error + await session.patch( + 'app/page.js', + ` export default function Page() { throw new Error('Server component error!') return

Hello world

} ` - ) + ) - expect(await session.hasRedbox(true)).toBe(true) + expect(await session.hasRedbox(true)).toBe(true) - await cleanup() + await cleanup() + }) }) -}) +} diff --git a/test/development/acceptance/ReactRefreshLogBox-app-doc.test.ts b/test/development/acceptance/ReactRefreshLogBox-app-doc.test.ts index 755ecdb416d2f..b58f06b0308b1 100644 --- a/test/development/acceptance/ReactRefreshLogBox-app-doc.test.ts +++ b/test/development/acceptance/ReactRefreshLogBox-app-doc.test.ts @@ -1,191 +1,194 @@ import { sandbox } from './helpers' import { createNext } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' +import { getSnapshotTestDescribe } from 'next-test-utils' -describe('ReactRefreshLogBox', () => { - let next: NextInstance +for (const variant of ['default', 'turbo']) { + getSnapshotTestDescribe(variant)(`ReactRefreshLogBox ${variant}`, () => { + let next: NextInstance - beforeAll(async () => { - next = await createNext({ - files: {}, - skipStart: true, + beforeAll(async () => { + next = await createNext({ + files: {}, + skipStart: true, + }) }) - }) - afterAll(() => next.destroy()) - - test('empty _app shows logbox', async () => { - const { session, cleanup } = await sandbox( - next, - new Map([ - [ - 'pages/_app.js', - ` - - `, - ], - ]) - ) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxDescription()).toMatchInlineSnapshot( - `"Error: The default export is not a React Component in page: \\"/_app\\""` - ) - - await session.patch( - 'pages/_app.js', - ` - function MyApp({ Component, pageProps }) { - return ; - } - export default MyApp - ` - ) - expect(await session.hasRedbox()).toBe(false) - await cleanup() - }) - - test('empty _document shows logbox', async () => { - const { session, cleanup } = await sandbox( - next, - new Map([ - [ - 'pages/_document.js', - ` - - `, - ], - ]) - ) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxDescription()).toMatchInlineSnapshot( - `"Error: The default export is not a React Component in page: \\"/_document\\""` - ) - - await session.patch( - 'pages/_document.js', - ` - import Document, { Html, Head, Main, NextScript } from 'next/document' - - class MyDocument extends Document { - static async getInitialProps(ctx) { - const initialProps = await Document.getInitialProps(ctx) - return { ...initialProps } - } - - render() { - return ( - - - -
- - - - ) + afterAll(() => next.destroy()) + + test('empty _app shows logbox', async () => { + const { session, cleanup } = await sandbox( + next, + new Map([ + [ + 'pages/_app.js', + ` + + `, + ], + ]) + ) + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxDescription()).toMatchInlineSnapshot( + `"Error: The default export is not a React Component in page: \\"/_app\\""` + ) + + await session.patch( + 'pages/_app.js', + ` + function MyApp({ Component, pageProps }) { + return ; } - } + export default MyApp + ` + ) + expect(await session.hasRedbox()).toBe(false) + await cleanup() + }) - export default MyDocument - ` - ) - expect(await session.hasRedbox()).toBe(false) - await cleanup() - }) + test('empty _document shows logbox', async () => { + const { session, cleanup } = await sandbox( + next, + new Map([ + [ + 'pages/_document.js', + ` + + `, + ], + ]) + ) + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxDescription()).toMatchInlineSnapshot( + `"Error: The default export is not a React Component in page: \\"/_document\\""` + ) + + await session.patch( + 'pages/_document.js', + ` + import Document, { Html, Head, Main, NextScript } from 'next/document' + + class MyDocument extends Document { + static async getInitialProps(ctx) { + const initialProps = await Document.getInitialProps(ctx) + return { ...initialProps } + } - test('_app syntax error shows logbox', async () => { - const { session, cleanup } = await sandbox( - next, - new Map([ - [ - 'pages/_app.js', - ` - function MyApp({ Component, pageProps }) { - return <; + render() { + return ( + + + +
+ + + + ) } - export default MyApp - `, - ], - ]) - ) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxSource()).toMatchSnapshot() - - await session.patch( - 'pages/_app.js', - ` - function MyApp({ Component, pageProps }) { - return ; - } - export default MyApp - ` - ) - expect(await session.hasRedbox()).toBe(false) - await cleanup() - }) + } - test('_document syntax error shows logbox', async () => { - const { session, cleanup } = await sandbox( - next, - new Map([ - [ - 'pages/_document.js', - ` - import Document, { Html, Head, Main, NextScript } from 'next/document' - - class MyDocument extends Document {{ - static async getInitialProps(ctx) { - const initialProps = await Document.getInitialProps(ctx) - return { ...initialProps } + export default MyDocument + ` + ) + expect(await session.hasRedbox()).toBe(false) + await cleanup() + }) + + test('_app syntax error shows logbox', async () => { + const { session, cleanup } = await sandbox( + next, + new Map([ + [ + 'pages/_app.js', + ` + function MyApp({ Component, pageProps }) { + return <; } + export default MyApp + `, + ], + ]) + ) + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxSource()).toMatchSnapshot() + + await session.patch( + 'pages/_app.js', + ` + function MyApp({ Component, pageProps }) { + return ; + } + export default MyApp + ` + ) + expect(await session.hasRedbox()).toBe(false) + await cleanup() + }) - render() { - return ( - - - -
- - - - ) + test('_document syntax error shows logbox', async () => { + const { session, cleanup } = await sandbox( + next, + new Map([ + [ + 'pages/_document.js', + ` + import Document, { Html, Head, Main, NextScript } from 'next/document' + + class MyDocument extends Document {{ + static async getInitialProps(ctx) { + const initialProps = await Document.getInitialProps(ctx) + return { ...initialProps } + } + + render() { + return ( + + + +
+ + + + ) + } } - } - export default MyDocument - `, - ], - ]) - ) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxSource()).toMatchSnapshot() - - await session.patch( - 'pages/_document.js', - ` - import Document, { Html, Head, Main, NextScript } from 'next/document' - - class MyDocument extends Document { - static async getInitialProps(ctx) { - const initialProps = await Document.getInitialProps(ctx) - return { ...initialProps } - } + export default MyDocument + `, + ], + ]) + ) + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxSource()).toMatchSnapshot() + + await session.patch( + 'pages/_document.js', + ` + import Document, { Html, Head, Main, NextScript } from 'next/document' + + class MyDocument extends Document { + static async getInitialProps(ctx) { + const initialProps = await Document.getInitialProps(ctx) + return { ...initialProps } + } - render() { - return ( - - - -
- - - - ) + render() { + return ( + + + +
+ + + + ) + } } - } - export default MyDocument - ` - ) - expect(await session.hasRedbox()).toBe(false) - await cleanup() + export default MyDocument + ` + ) + expect(await session.hasRedbox()).toBe(false) + await cleanup() + }) }) -}) +} diff --git a/test/development/acceptance/ReactRefreshLogBox-builtins.test.ts b/test/development/acceptance/ReactRefreshLogBox-builtins.test.ts index 87a2439ea3add..ae18392eb4e1b 100644 --- a/test/development/acceptance/ReactRefreshLogBox-builtins.test.ts +++ b/test/development/acceptance/ReactRefreshLogBox-builtins.test.ts @@ -1,148 +1,151 @@ import { sandbox } from './helpers' import { createNext } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' +import { getSnapshotTestDescribe } from 'next-test-utils' -describe('ReactRefreshLogBox', () => { - let next: NextInstance +for (const variant of ['default', 'turbo']) { + getSnapshotTestDescribe(variant)(`ReactRefreshLogBox ${variant}`, () => { + let next: NextInstance - beforeAll(async () => { - next = await createNext({ - files: {}, - skipStart: true, + beforeAll(async () => { + next = await createNext({ + files: {}, + skipStart: true, + }) }) - }) - afterAll(() => next.destroy()) - - // Module trace is only available with webpack 5 - test('Node.js builtins', async () => { - const { session, cleanup } = await sandbox( - next, - new Map([ - [ - 'node_modules/my-package/index.js', - ` - const dns = require('dns') - module.exports = dns - `, - ], - [ - 'node_modules/my-package/package.json', - ` - { - "name": "my-package", - "version": "0.0.1" - } - `, - ], - ]) - ) - - await session.patch( - 'index.js', + afterAll(() => next.destroy()) + + // Module trace is only available with webpack 5 + test('Node.js builtins', async () => { + const { session, cleanup } = await sandbox( + next, + new Map([ + [ + 'node_modules/my-package/index.js', + ` + const dns = require('dns') + module.exports = dns + `, + ], + [ + 'node_modules/my-package/package.json', + ` + { + "name": "my-package", + "version": "0.0.1" + } + `, + ], + ]) + ) + + await session.patch( + 'index.js', + ` + import pkg from 'my-package' + + export default function Hello() { + return (pkg ?

Package loaded

:

Package did not load

) + } ` - import pkg from 'my-package' - - export default function Hello() { - return (pkg ?

Package loaded

:

Package did not load

) - } - ` - ) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxSource()).toMatchSnapshot() + ) + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxSource()).toMatchSnapshot() - await cleanup() - }) + await cleanup() + }) - test('Module not found', async () => { - const { session, cleanup } = await sandbox(next) + test('Module not found', async () => { + const { session, cleanup } = await sandbox(next) + + await session.patch( + 'index.js', + `import Comp from 'b' + export default function Oops() { + return ( +
+ lol +
+ ) + } + ` + ) - await session.patch( - 'index.js', - `import Comp from 'b' - export default function Oops() { - return ( -
- lol -
- ) - } - ` - ) + expect(await session.hasRedbox(true)).toBe(true) - expect(await session.hasRedbox(true)).toBe(true) + const source = await session.getRedboxSource() + expect(source).toMatchSnapshot() - const source = await session.getRedboxSource() - expect(source).toMatchSnapshot() + await cleanup() + }) - await cleanup() - }) + test('Module not found (empty import trace)', async () => { + const { session, cleanup } = await sandbox(next) + + await session.patch( + 'pages/index.js', + `import Comp from 'b' + export default function Oops() { + return ( +
+ lol +
+ ) + } + ` + ) - test('Module not found (empty import trace)', async () => { - const { session, cleanup } = await sandbox(next) + expect(await session.hasRedbox(true)).toBe(true) - await session.patch( - 'pages/index.js', - `import Comp from 'b' - export default function Oops() { - return ( -
- lol -
- ) - } - ` - ) + const source = await session.getRedboxSource() + expect(source).toMatchSnapshot() - expect(await session.hasRedbox(true)).toBe(true) + await cleanup() + }) - const source = await session.getRedboxSource() - expect(source).toMatchSnapshot() + test('Module not found (missing global CSS)', async () => { + const { session, cleanup } = await sandbox( + next, + new Map([ + [ + 'pages/_app.js', + ` + import './non-existent.css' + + export default function App({ Component, pageProps }) { + return + } + `, + ], + [ + 'pages/index.js', + ` + export default function Page(props) { + return

index page

+ } + `, + ], + ]) + ) + expect(await session.hasRedbox(true)).toBe(true) - await cleanup() - }) + const source = await session.getRedboxSource() + expect(source).toMatchSnapshot() - test('Module not found (missing global CSS)', async () => { - const { session, cleanup } = await sandbox( - next, - new Map([ - [ - 'pages/_app.js', - ` - import './non-existent.css' - + await session.patch( + 'pages/_app.js', + ` export default function App({ Component, pageProps }) { return } - `, - ], - [ - 'pages/index.js', - ` - export default function Page(props) { - return

index page

- } - `, - ], - ]) - ) - expect(await session.hasRedbox(true)).toBe(true) - - const source = await session.getRedboxSource() - expect(source).toMatchSnapshot() - - await session.patch( - 'pages/_app.js', ` - export default function App({ Component, pageProps }) { - return - } - ` - ) - expect(await session.hasRedbox(false)).toBe(false) - expect( - await session.evaluate(() => document.documentElement.innerHTML) - ).toContain('index page') - - await cleanup() + ) + expect(await session.hasRedbox(false)).toBe(false) + expect( + await session.evaluate(() => document.documentElement.innerHTML) + ).toContain('index page') + + await cleanup() + }) }) -}) +} diff --git a/test/development/acceptance/ReactRefreshLogBox.test.ts b/test/development/acceptance/ReactRefreshLogBox.test.ts index d9aced58dba6f..7fdb666390d70 100644 --- a/test/development/acceptance/ReactRefreshLogBox.test.ts +++ b/test/development/acceptance/ReactRefreshLogBox.test.ts @@ -2,29 +2,30 @@ import { sandbox } from './helpers' import { createNext } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' -import { check } from 'next-test-utils' +import { check, getSnapshotTestDescribe } from 'next-test-utils' -describe('ReactRefreshLogBox', () => { - let next: NextInstance +for (const variant of ['default', 'turbo']) { + getSnapshotTestDescribe(variant)(`ReactRefreshLogBox ${variant}`, () => { + let next: NextInstance - beforeAll(async () => { - next = await createNext({ - files: {}, - skipStart: true, + beforeAll(async () => { + next = await createNext({ + files: {}, + skipStart: true, + }) }) - }) - afterAll(() => next.destroy()) + afterAll(() => next.destroy()) - test('should strip whitespace correctly with newline', async () => { - const { session, cleanup } = await sandbox(next) + test('should strip whitespace correctly with newline', async () => { + const { session, cleanup } = await sandbox(next) - await session.patch( - 'index.js', - ` + await session.patch( + 'index.js', + ` export default function Page() { return ( <> - +

index page

{ @@ -36,21 +37,21 @@ describe('ReactRefreshLogBox', () => { ) } ` - ) - await session.evaluate(() => document.querySelector('a').click()) + ) + await session.evaluate(() => document.querySelector('a').click()) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxSource()).toMatchSnapshot() + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxSource()).toMatchSnapshot() - await cleanup() - }) + await cleanup() + }) - test('logbox: can recover from a syntax error without losing state', async () => { - const { session, cleanup } = await sandbox(next) + test('logbox: can recover from a syntax error without losing state', async () => { + const { session, cleanup } = await sandbox(next) - await session.patch( - 'index.js', - ` + await session.patch( + 'index.js', + ` import { useCallback, useState } from 'react' export default function Index() { @@ -64,21 +65,21 @@ describe('ReactRefreshLogBox', () => { ) } ` - ) + ) - await session.evaluate(() => document.querySelector('button').click()) - expect( - await session.evaluate(() => document.querySelector('p').textContent) - ).toBe('1') + await session.evaluate(() => document.querySelector('button').click()) + expect( + await session.evaluate(() => document.querySelector('p').textContent) + ).toBe('1') - await session.patch('index.js', `export default () =>
{ ) } ` - ) + ) - await check( - () => session.evaluate(() => document.querySelector('p').textContent), - /Count: 1/ - ) + await check( + () => session.evaluate(() => document.querySelector('p').textContent), + /Count: 1/ + ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox()).toBe(false) - await cleanup() - }) + await cleanup() + }) - test('logbox: can recover from a event handler error', async () => { - const { session, cleanup } = await sandbox(next) + test('logbox: can recover from a event handler error', async () => { + const { session, cleanup } = await sandbox(next) - await session.patch( - 'index.js', - ` + await session.patch( + 'index.js', + ` import { useCallback, useState } from 'react' export default function Index() { @@ -126,26 +127,26 @@ describe('ReactRefreshLogBox', () => { ) } ` - ) - - expect( - await session.evaluate(() => document.querySelector('p').textContent) - ).toBe('0') - await session.evaluate(() => document.querySelector('button').click()) - expect( - await session.evaluate(() => document.querySelector('p').textContent) - ).toBe('1') - - expect(await session.hasRedbox(true)).toBe(true) - if (process.platform === 'win32') { - expect(await session.getRedboxSource()).toMatchSnapshot() - } else { - expect(await session.getRedboxSource()).toMatchSnapshot() - } + ) - await session.patch( - 'index.js', - ` + expect( + await session.evaluate(() => document.querySelector('p').textContent) + ).toBe('0') + await session.evaluate(() => document.querySelector('button').click()) + expect( + await session.evaluate(() => document.querySelector('p').textContent) + ).toBe('1') + + expect(await session.hasRedbox(true)).toBe(true) + if (process.platform === 'win32') { + expect(await session.getRedboxSource()).toMatchSnapshot() + } else { + expect(await session.getRedboxSource()).toMatchSnapshot() + } + + await session.patch( + 'index.js', + ` import { useCallback, useState } from 'react' export default function Index() { @@ -159,38 +160,38 @@ describe('ReactRefreshLogBox', () => { ) } ` - ) + ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox()).toBe(false) - expect( - await session.evaluate(() => document.querySelector('p').textContent) - ).toBe('Count: 1') - await session.evaluate(() => document.querySelector('button').click()) - expect( - await session.evaluate(() => document.querySelector('p').textContent) - ).toBe('Count: 2') + expect( + await session.evaluate(() => document.querySelector('p').textContent) + ).toBe('Count: 1') + await session.evaluate(() => document.querySelector('button').click()) + expect( + await session.evaluate(() => document.querySelector('p').textContent) + ).toBe('Count: 2') - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox()).toBe(false) - await cleanup() - }) + await cleanup() + }) - test('logbox: can recover from a component error', async () => { - const { session, cleanup } = await sandbox(next) + test('logbox: can recover from a component error', async () => { + const { session, cleanup } = await sandbox(next) - await session.write( - 'child.js', - ` + await session.write( + 'child.js', + ` export default function Child() { return

Hello

; } ` - ) + ) - await session.patch( - 'index.js', - ` + await session.patch( + 'index.js', + ` import Child from './child' export default function Index() { @@ -201,51 +202,51 @@ describe('ReactRefreshLogBox', () => { ) } ` - ) + ) - expect( - await session.evaluate(() => document.querySelector('p').textContent) - ).toBe('Hello') + expect( + await session.evaluate(() => document.querySelector('p').textContent) + ).toBe('Hello') - await session.patch( - 'child.js', - ` + await session.patch( + 'child.js', + ` // hello export default function Child() { throw new Error('oops') } ` - ) + ) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxSource()).toMatchSnapshot() + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxSource()).toMatchSnapshot() - const didNotReload = await session.patch( - 'child.js', - ` + const didNotReload = await session.patch( + 'child.js', + ` export default function Child() { return

Hello

; } ` - ) + ) - expect(didNotReload).toBe(true) - expect(await session.hasRedbox()).toBe(false) - expect( - await session.evaluate(() => document.querySelector('p').textContent) - ).toBe('Hello') + expect(didNotReload).toBe(true) + expect(await session.hasRedbox()).toBe(false) + expect( + await session.evaluate(() => document.querySelector('p').textContent) + ).toBe('Hello') - await cleanup() - }) + await cleanup() + }) - // https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554137262 - test('render error not shown right after syntax error', async () => { - const { session, cleanup } = await sandbox(next) + // https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554137262 + test('render error not shown right after syntax error', async () => { + const { session, cleanup } = await sandbox(next) - // Starting here: - await session.patch( - 'index.js', - ` + // Starting here: + await session.patch( + 'index.js', + ` import * as React from 'react'; class ClassDefault extends React.Component { render() { @@ -255,16 +256,16 @@ describe('ReactRefreshLogBox', () => { export default ClassDefault; ` - ) + ) - expect( - await session.evaluate(() => document.querySelector('h1').textContent) - ).toBe('Default Export') + expect( + await session.evaluate(() => document.querySelector('h1').textContent) + ).toBe('Default Export') - // Break it with a syntax error: - await session.patch( - 'index.js', - ` + // Break it with a syntax error: + await session.patch( + 'index.js', + ` import * as React from 'react'; class ClassDefault extends React.Component { @@ -275,13 +276,13 @@ describe('ReactRefreshLogBox', () => { export default ClassDefault; ` - ) - expect(await session.hasRedbox(true)).toBe(true) + ) + expect(await session.hasRedbox(true)).toBe(true) - // Now change the code to introduce a runtime error without fixing the syntax error: - await session.patch( - 'index.js', - ` + // Now change the code to introduce a runtime error without fixing the syntax error: + await session.patch( + 'index.js', + ` import * as React from 'react'; class ClassDefault extends React.Component { @@ -293,13 +294,13 @@ describe('ReactRefreshLogBox', () => { export default ClassDefault; ` - ) - expect(await session.hasRedbox(true)).toBe(true) + ) + expect(await session.hasRedbox(true)).toBe(true) - // Now fix the syntax error: - await session.patch( - 'index.js', - ` + // Now fix the syntax error: + await session.patch( + 'index.js', + ` import * as React from 'react'; class ClassDefault extends React.Component { @@ -311,22 +312,22 @@ describe('ReactRefreshLogBox', () => { export default ClassDefault; ` - ) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxSource()).toMatchSnapshot() + ) + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxSource()).toMatchSnapshot() - await cleanup() - }) + await cleanup() + }) - // https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554137807 - test('module init error not shown', async () => { - // Start here: - const { session, cleanup } = await sandbox(next) + // https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554137807 + test('module init error not shown', async () => { + // Start here: + const { session, cleanup } = await sandbox(next) - // We start here. - await session.patch( - 'index.js', - ` + // We start here. + await session.patch( + 'index.js', + ` import * as React from 'react'; class ClassDefault extends React.Component { render() { @@ -335,16 +336,16 @@ describe('ReactRefreshLogBox', () => { } export default ClassDefault; ` - ) + ) - expect( - await session.evaluate(() => document.querySelector('h1').textContent) - ).toBe('Default Export') + expect( + await session.evaluate(() => document.querySelector('h1').textContent) + ).toBe('Default Export') - // Add a throw in module init phase: - await session.patch( - 'index.js', - ` + // Add a throw in module init phase: + await session.patch( + 'index.js', + ` // top offset for snapshot import * as React from 'react'; throw new Error('no') @@ -355,22 +356,22 @@ describe('ReactRefreshLogBox', () => { } export default ClassDefault; ` - ) + ) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxSource()).toMatchSnapshot() + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxSource()).toMatchSnapshot() - await cleanup() - }) + await cleanup() + }) - // https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554144016 - test('stuck error', async () => { - const { session, cleanup } = await sandbox(next) + // https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554144016 + test('stuck error', async () => { + const { session, cleanup } = await sandbox(next) - // We start here. - await session.patch( - 'index.js', - ` + // We start here. + await session.patch( + 'index.js', + ` import * as React from 'react'; function FunctionDefault() { @@ -379,23 +380,23 @@ describe('ReactRefreshLogBox', () => { export default FunctionDefault; ` - ) + ) - // We add a new file. Let's call it Foo.js. - await session.write( - 'Foo.js', - ` + // We add a new file. Let's call it Foo.js. + await session.write( + 'Foo.js', + ` // intentionally skips export export default function Foo() { return React.createElement('h1', null, 'Foo'); } ` - ) + ) - // We edit our first file to use it. - await session.patch( - 'index.js', - ` + // We edit our first file to use it. + await session.patch( + 'index.js', + ` import * as React from 'react'; import Foo from './Foo'; function FunctionDefault() { @@ -403,48 +404,48 @@ describe('ReactRefreshLogBox', () => { } export default FunctionDefault; ` - ) + ) - // We get an error because Foo didn't import React. Fair. - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxSource()).toMatchSnapshot() + // We get an error because Foo didn't import React. Fair. + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxSource()).toMatchSnapshot() - // Let's add that to Foo. - await session.patch( - 'Foo.js', - ` + // Let's add that to Foo. + await session.patch( + 'Foo.js', + ` import * as React from 'react'; export default function Foo() { return React.createElement('h1', null, 'Foo'); } ` - ) + ) - // Expected: this fixes the problem - expect(await session.hasRedbox()).toBe(false) + // Expected: this fixes the problem + expect(await session.hasRedbox()).toBe(false) - await cleanup() - }) + await cleanup() + }) - // https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554150098 - test('syntax > runtime error', async () => { - const { session, cleanup } = await sandbox(next) + // https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554150098 + test('syntax > runtime error', async () => { + const { session, cleanup } = await sandbox(next) - // Start here. - await session.patch( - 'index.js', - ` + // Start here. + await session.patch( + 'index.js', + ` import * as React from 'react'; export default function FunctionNamed() { return
} ` - ) - // TODO: this acts weird without above step - await session.patch( - 'index.js', - ` + ) + // TODO: this acts weird without above step + await session.patch( + 'index.js', + ` import * as React from 'react'; let i = 0 setInterval(() => { @@ -455,20 +456,20 @@ describe('ReactRefreshLogBox', () => { return
} ` - ) - - await new Promise((resolve) => setTimeout(resolve, 1000)) - expect(await session.hasRedbox(true)).toBe(true) - if (process.platform === 'win32') { - expect(await session.getRedboxSource()).toMatchSnapshot() - } else { - expect(await session.getRedboxSource()).toMatchSnapshot() - } + ) - // Make a syntax error. - await session.patch( - 'index.js', - ` + await new Promise((resolve) => setTimeout(resolve, 1000)) + expect(await session.hasRedbox(true)).toBe(true) + if (process.platform === 'win32') { + expect(await session.getRedboxSource()).toMatchSnapshot() + } else { + expect(await session.getRedboxSource()).toMatchSnapshot() + } + + // Make a syntax error. + await session.patch( + 'index.js', + ` import * as React from 'react'; let i = 0 setInterval(() => { @@ -476,35 +477,35 @@ describe('ReactRefreshLogBox', () => { throw Error('no ' + i) }, 1000) export default function FunctionNamed() {` - ) + ) - await new Promise((resolve) => setTimeout(resolve, 1000)) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxSource()).toMatchSnapshot() + await new Promise((resolve) => setTimeout(resolve, 1000)) + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxSource()).toMatchSnapshot() - // Test that runtime error does not take over: - await new Promise((resolve) => setTimeout(resolve, 2000)) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxSource()).toMatchSnapshot() + // Test that runtime error does not take over: + await new Promise((resolve) => setTimeout(resolve, 2000)) + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxSource()).toMatchSnapshot() - await cleanup() - }) + await cleanup() + }) - // https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554152127 - test('boundaries', async () => { - const { session, cleanup } = await sandbox(next) + // https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554152127 + test('boundaries', async () => { + const { session, cleanup } = await sandbox(next) - await session.write( - 'FunctionDefault.js', - ` + await session.write( + 'FunctionDefault.js', + ` export default function FunctionDefault() { return

hello

} ` - ) - await session.patch( - 'index.js', - ` + ) + await session.patch( + 'index.js', + ` import FunctionDefault from './FunctionDefault.js' import * as React from 'react' class ErrorBoundary extends React.Component { @@ -534,58 +535,58 @@ describe('ReactRefreshLogBox', () => { } export default App; ` - ) + ) - expect( - await session.evaluate(() => document.querySelector('h2').textContent) - ).toBe('hello') + expect( + await session.evaluate(() => document.querySelector('h2').textContent) + ).toBe('hello') - await session.write( - 'FunctionDefault.js', - `export default function FunctionDefault() { throw new Error('no'); }` - ) + await session.write( + 'FunctionDefault.js', + `export default function FunctionDefault() { throw new Error('no'); }` + ) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxSource()).toMatchSnapshot() - expect( - await session.evaluate(() => document.querySelector('h2').textContent) - ).toBe('error') + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxSource()).toMatchSnapshot() + expect( + await session.evaluate(() => document.querySelector('h2').textContent) + ).toBe('error') - await cleanup() - }) + await cleanup() + }) - // TODO: investigate why this fails when running outside of the Next.js - // monorepo e.g. fails when using yarn create next-app - // https://github.com/vercel/next.js/pull/23203 - test.skip('internal package errors', async () => { - const { session, cleanup } = await sandbox(next) + // TODO: investigate why this fails when running outside of the Next.js + // monorepo e.g. fails when using yarn create next-app + // https://github.com/vercel/next.js/pull/23203 + test.skip('internal package errors', async () => { + const { session, cleanup } = await sandbox(next) - // Make a react build-time error. - await session.patch( - 'index.js', - ` + // Make a react build-time error. + await session.patch( + 'index.js', + ` export default function FunctionNamed() { return
{{}}
}` - ) + ) - expect(await session.hasRedbox(true)).toBe(true) - // We internally only check the script path, not including the line number - // and error message because the error comes from an external library. - // This test ensures that the errored script path is correctly resolved. - expect(await session.getRedboxSource()).toContain( - `../../../../packages/next/dist/pages/_document.js` - ) + expect(await session.hasRedbox(true)).toBe(true) + // We internally only check the script path, not including the line number + // and error message because the error comes from an external library. + // This test ensures that the errored script path is correctly resolved. + expect(await session.getRedboxSource()).toContain( + `../../../../packages/next/dist/pages/_document.js` + ) - await cleanup() - }) + await cleanup() + }) - test('unterminated JSX', async () => { - const { session, cleanup } = await sandbox(next) + test('unterminated JSX', async () => { + const { session, cleanup } = await sandbox(next) - await session.patch( - 'index.js', - ` + await session.patch( + 'index.js', + ` export default () => { return (
@@ -594,13 +595,13 @@ describe('ReactRefreshLogBox', () => { ) } ` - ) + ) - expect(await session.hasRedbox()).toBe(false) + expect(await session.hasRedbox()).toBe(false) - await session.patch( - 'index.js', - ` + await session.patch( + 'index.js', + ` export default () => { return (
@@ -609,32 +610,32 @@ describe('ReactRefreshLogBox', () => { ) } ` - ) + ) - expect(await session.hasRedbox(true)).toBe(true) + expect(await session.hasRedbox(true)).toBe(true) - const source = await session.getRedboxSource() - expect(source).toMatchSnapshot() + const source = await session.getRedboxSource() + expect(source).toMatchSnapshot() - await cleanup() - }) + await cleanup() + }) - // Module trace is only available with webpack 5 - test('conversion to class component (1)', async () => { - const { session, cleanup } = await sandbox(next) + // Module trace is only available with webpack 5 + test('conversion to class component (1)', async () => { + const { session, cleanup } = await sandbox(next) - await session.write( - 'Child.js', - ` + await session.write( + 'Child.js', + ` export default function ClickCount() { return

hello

} ` - ) + ) - await session.patch( - 'index.js', - ` + await session.patch( + 'index.js', + ` import Child from './Child'; export default function Home() { @@ -645,16 +646,16 @@ describe('ReactRefreshLogBox', () => { ) } ` - ) + ) - expect(await session.hasRedbox()).toBe(false) - expect( - await session.evaluate(() => document.querySelector('p').textContent) - ).toBe('hello') + expect(await session.hasRedbox()).toBe(false) + expect( + await session.evaluate(() => document.querySelector('p').textContent) + ).toBe('hello') - await session.patch( - 'Child.js', - ` + await session.patch( + 'Child.js', + ` import { Component } from 'react'; export default class ClickCount extends Component { render() { @@ -662,14 +663,14 @@ describe('ReactRefreshLogBox', () => { } } ` - ) + ) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxSource()).toMatchSnapshot() + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxSource()).toMatchSnapshot() - await session.patch( - 'Child.js', - ` + await session.patch( + 'Child.js', + ` import { Component } from 'react'; export default class ClickCount extends Component { render() { @@ -677,23 +678,23 @@ describe('ReactRefreshLogBox', () => { } } ` - ) + ) - expect(await session.hasRedbox()).toBe(false) - expect( - await session.evaluate(() => document.querySelector('p').textContent) - ).toBe('hello new') + expect(await session.hasRedbox()).toBe(false) + expect( + await session.evaluate(() => document.querySelector('p').textContent) + ).toBe('hello new') - await cleanup() - }) + await cleanup() + }) - test('css syntax errors', async () => { - const { session, cleanup } = await sandbox(next) + test('css syntax errors', async () => { + const { session, cleanup } = await sandbox(next) - await session.write('index.module.css', `.button {}`) - await session.patch( - 'index.js', - ` + await session.write('index.module.css', `.button {}`) + await session.patch( + 'index.js', + ` import './index.module.css'; export default () => { return ( @@ -703,35 +704,35 @@ describe('ReactRefreshLogBox', () => { ) } ` - ) - - expect(await session.hasRedbox()).toBe(false) - - // Syntax error - await session.patch('index.module.css', `.button {`) - expect(await session.hasRedbox(true)).toBe(true) - const source = await session.getRedboxSource() - expect(source).toMatch('./index.module.css:1:1') - expect(source).toMatch('Syntax error: ') - expect(source).toMatch('Unclosed block') - expect(source).toMatch('> 1 | .button {') - expect(source).toMatch(' | ^') - - // Not local error - await session.patch('index.module.css', `button {}`) - expect(await session.hasRedbox(true)).toBe(true) - const source2 = await session.getRedboxSource() - expect(source2).toMatchSnapshot() - - await cleanup() - }) + ) - test('logbox: anchors links in error messages', async () => { - const { session, cleanup } = await sandbox(next) + expect(await session.hasRedbox()).toBe(false) + + // Syntax error + await session.patch('index.module.css', `.button {`) + expect(await session.hasRedbox(true)).toBe(true) + const source = await session.getRedboxSource() + expect(source).toMatch('./index.module.css:1:1') + expect(source).toMatch('Syntax error: ') + expect(source).toMatch('Unclosed block') + expect(source).toMatch('> 1 | .button {') + expect(source).toMatch(' | ^') + + // Not local error + await session.patch('index.module.css', `button {}`) + expect(await session.hasRedbox(true)).toBe(true) + const source2 = await session.getRedboxSource() + expect(source2).toMatchSnapshot() + + await cleanup() + }) - await session.patch( - 'index.js', - ` + test('logbox: anchors links in error messages', async () => { + const { session, cleanup } = await sandbox(next) + + await session.patch( + 'index.js', + ` import { useCallback } from 'react' export default function Index() { @@ -745,39 +746,39 @@ describe('ReactRefreshLogBox', () => { ) } ` - ) - - expect(await session.hasRedbox()).toBe(false) - await session.evaluate(() => document.querySelector('button').click()) - expect(await session.hasRedbox(true)).toBe(true) - - const header = await session.getRedboxDescription() - expect(header).toMatchSnapshot() - expect( - await session.evaluate( - () => - document - .querySelector('body > nextjs-portal') - .shadowRoot.querySelectorAll('#nextjs__container_errors_desc a') - .length ) - ).toBe(1) - expect( - await session.evaluate( - () => - ( + + expect(await session.hasRedbox()).toBe(false) + await session.evaluate(() => document.querySelector('button').click()) + expect(await session.hasRedbox(true)).toBe(true) + + const header = await session.getRedboxDescription() + expect(header).toMatchSnapshot() + expect( + await session.evaluate( + () => document .querySelector('body > nextjs-portal') - .shadowRoot.querySelector( - '#nextjs__container_errors_desc a:nth-of-type(1)' - ) as any - ).href - ) - ).toMatchSnapshot() - - await session.patch( - 'index.js', - ` + .shadowRoot.querySelectorAll('#nextjs__container_errors_desc a') + .length + ) + ).toBe(1) + expect( + await session.evaluate( + () => + ( + document + .querySelector('body > nextjs-portal') + .shadowRoot.querySelector( + '#nextjs__container_errors_desc a:nth-of-type(1)' + ) as any + ).href + ) + ).toMatchSnapshot() + + await session.patch( + 'index.js', + ` import { useCallback } from 'react' export default function Index() { @@ -791,39 +792,39 @@ describe('ReactRefreshLogBox', () => { ) } ` - ) - - expect(await session.hasRedbox()).toBe(false) - await session.evaluate(() => document.querySelector('button').click()) - expect(await session.hasRedbox(true)).toBe(true) - - const header2 = await session.getRedboxDescription() - expect(header2).toMatchSnapshot() - expect( - await session.evaluate( - () => - document - .querySelector('body > nextjs-portal') - .shadowRoot.querySelectorAll('#nextjs__container_errors_desc a') - .length ) - ).toBe(1) - expect( - await session.evaluate( - () => - ( + + expect(await session.hasRedbox()).toBe(false) + await session.evaluate(() => document.querySelector('button').click()) + expect(await session.hasRedbox(true)).toBe(true) + + const header2 = await session.getRedboxDescription() + expect(header2).toMatchSnapshot() + expect( + await session.evaluate( + () => document .querySelector('body > nextjs-portal') - .shadowRoot.querySelector( - '#nextjs__container_errors_desc a:nth-of-type(1)' - ) as any - ).href - ) - ).toMatchSnapshot() - - await session.patch( - 'index.js', - ` + .shadowRoot.querySelectorAll('#nextjs__container_errors_desc a') + .length + ) + ).toBe(1) + expect( + await session.evaluate( + () => + ( + document + .querySelector('body > nextjs-portal') + .shadowRoot.querySelector( + '#nextjs__container_errors_desc a:nth-of-type(1)' + ) as any + ).href + ) + ).toMatchSnapshot() + + await session.patch( + 'index.js', + ` import { useCallback } from 'react' export default function Index() { @@ -837,39 +838,39 @@ describe('ReactRefreshLogBox', () => { ) } ` - ) - - expect(await session.hasRedbox()).toBe(false) - await session.evaluate(() => document.querySelector('button').click()) - expect(await session.hasRedbox(true)).toBe(true) - - const header3 = await session.getRedboxDescription() - expect(header3).toMatchSnapshot() - expect( - await session.evaluate( - () => - document - .querySelector('body > nextjs-portal') - .shadowRoot.querySelectorAll('#nextjs__container_errors_desc a') - .length ) - ).toBe(1) - expect( - await session.evaluate( - () => - ( + + expect(await session.hasRedbox()).toBe(false) + await session.evaluate(() => document.querySelector('button').click()) + expect(await session.hasRedbox(true)).toBe(true) + + const header3 = await session.getRedboxDescription() + expect(header3).toMatchSnapshot() + expect( + await session.evaluate( + () => document .querySelector('body > nextjs-portal') - .shadowRoot.querySelector( - '#nextjs__container_errors_desc a:nth-of-type(1)' - ) as any - ).href - ) - ).toMatchSnapshot() - - await session.patch( - 'index.js', - ` + .shadowRoot.querySelectorAll('#nextjs__container_errors_desc a') + .length + ) + ).toBe(1) + expect( + await session.evaluate( + () => + ( + document + .querySelector('body > nextjs-portal') + .shadowRoot.querySelector( + '#nextjs__container_errors_desc a:nth-of-type(1)' + ) as any + ).href + ) + ).toMatchSnapshot() + + await session.patch( + 'index.js', + ` import { useCallback } from 'react' export default function Index() { @@ -883,59 +884,59 @@ describe('ReactRefreshLogBox', () => { ) } ` - ) - - expect(await session.hasRedbox()).toBe(false) - await session.evaluate(() => document.querySelector('button').click()) - expect(await session.hasRedbox(true)).toBe(true) - - const header4 = await session.getRedboxDescription() - expect(header4).toMatchInlineSnapshot( - `"Error: multiple http://nextjs.org links http://example.com"` - ) - expect( - await session.evaluate( - () => - document - .querySelector('body > nextjs-portal') - .shadowRoot.querySelectorAll('#nextjs__container_errors_desc a') - .length ) - ).toBe(2) - expect( - await session.evaluate( - () => - ( - document - .querySelector('body > nextjs-portal') - .shadowRoot.querySelector( - '#nextjs__container_errors_desc a:nth-of-type(1)' - ) as any - ).href + + expect(await session.hasRedbox()).toBe(false) + await session.evaluate(() => document.querySelector('button').click()) + expect(await session.hasRedbox(true)).toBe(true) + + const header4 = await session.getRedboxDescription() + expect(header4).toMatchInlineSnapshot( + `"Error: multiple http://nextjs.org links http://example.com"` ) - ).toMatchSnapshot() - expect( - await session.evaluate( - () => - ( + expect( + await session.evaluate( + () => document .querySelector('body > nextjs-portal') - .shadowRoot.querySelector( - '#nextjs__container_errors_desc a:nth-of-type(2)' - ) as any - ).href - ) - ).toMatchSnapshot() - - await cleanup() - }) + .shadowRoot.querySelectorAll('#nextjs__container_errors_desc a') + .length + ) + ).toBe(2) + expect( + await session.evaluate( + () => + ( + document + .querySelector('body > nextjs-portal') + .shadowRoot.querySelector( + '#nextjs__container_errors_desc a:nth-of-type(1)' + ) as any + ).href + ) + ).toMatchSnapshot() + expect( + await session.evaluate( + () => + ( + document + .querySelector('body > nextjs-portal') + .shadowRoot.querySelector( + '#nextjs__container_errors_desc a:nth-of-type(2)' + ) as any + ).href + ) + ).toMatchSnapshot() + + await cleanup() + }) - test('non-Error errors are handled properly', async () => { - const { session, cleanup } = await sandbox(next) + test('non-Error errors are handled properly', async () => { + const { session, cleanup } = await sandbox(next) - await session.patch( - 'index.js', - ` + await session.patch( + 'index.js', + ` export default () => { throw {'a': 1, 'b': 'x'}; return ( @@ -943,30 +944,30 @@ describe('ReactRefreshLogBox', () => { ) } ` - ) + ) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxDescription()).toMatchInlineSnapshot( - `"Error: {\\"a\\":1,\\"b\\":\\"x\\"}"` - ) + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxDescription()).toMatchInlineSnapshot( + `"Error: {\\"a\\":1,\\"b\\":\\"x\\"}"` + ) - // fix previous error - await session.patch( - 'index.js', - ` + // fix previous error + await session.patch( + 'index.js', + ` export default () => { return (
hello
) } ` - ) - expect(await session.hasRedbox(false)).toBe(false) - await session.patch( - 'index.js', - ` + ) + expect(await session.hasRedbox(false)).toBe(false) + await session.patch( + 'index.js', + ` class Hello {} - + export default () => { throw Hello return ( @@ -974,27 +975,27 @@ describe('ReactRefreshLogBox', () => { ) } ` - ) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxDescription()).toContain( - `Error: class Hello {` - ) - - // fix previous error - await session.patch( - 'index.js', - ` + ) + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxDescription()).toContain( + `Error: class Hello {` + ) + + // fix previous error + await session.patch( + 'index.js', + ` export default () => { return (
hello
) } ` - ) - expect(await session.hasRedbox(false)).toBe(false) - await session.patch( - 'index.js', - ` + ) + expect(await session.hasRedbox(false)).toBe(false) + await session.patch( + 'index.js', + ` export default () => { throw "string error" return ( @@ -1002,27 +1003,27 @@ describe('ReactRefreshLogBox', () => { ) } ` - ) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxDescription()).toMatchInlineSnapshot( - `"Error: string error"` - ) - - // fix previous error - await session.patch( - 'index.js', - ` + ) + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxDescription()).toMatchInlineSnapshot( + `"Error: string error"` + ) + + // fix previous error + await session.patch( + 'index.js', + ` export default () => { return (
hello
) } ` - ) - expect(await session.hasRedbox(false)).toBe(false) - await session.patch( - 'index.js', - ` + ) + expect(await session.hasRedbox(false)).toBe(false) + await session.patch( + 'index.js', + ` export default () => { throw null return ( @@ -1030,12 +1031,13 @@ describe('ReactRefreshLogBox', () => { ) } ` - ) - expect(await session.hasRedbox(true)).toBe(true) - expect(await session.getRedboxDescription()).toContain( - `Error: A null error was thrown` - ) + ) + expect(await session.hasRedbox(true)).toBe(true) + expect(await session.getRedboxDescription()).toContain( + `Error: A null error was thrown` + ) - await cleanup() + await cleanup() + }) }) -}) +} diff --git a/test/lib/next-test-utils.js b/test/lib/next-test-utils.js index ef2b9552c52d8..4d30567a8db35 100644 --- a/test/lib/next-test-utils.js +++ b/test/lib/next-test-utils.js @@ -920,3 +920,20 @@ export function shouldRunTurboDevTest() { // If the test path matches the glob pattern, add additional case to run the test with `--turbo` flag. return isMatch } + +// WEB-168: There are some differences / incompletes in turbopack implementation enforces jest requires to update +// test snapshot when run against turbo. This fn returns describe, or describe.skip dependes on the running context +// to avoid force-snapshot update per each runs until turbopack update includes all the changes. +export function getSnapshotTestDescribe(variant) { + const runningEnv = variant ?? 'default' + if (runningEnv !== 'default' && runningEnv !== 'turbo') { + throw new Error(`Check if test env passed correctly ${variant}`) + } + + const shouldRunTurboDev = shouldRunTurboDevTest() + const shouldSkip = + (runningEnv === 'turbo' && !shouldRunTurboDev) || + (runningEnv === 'default' && shouldRunTurboDev) + + return shouldSkip ? describe.skip : describe +}