/* eslint-env jest */ import { sandbox } from './helpers' import { createNext } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' import { getSnapshotTestDescribe } from 'next-test-utils' for (const variant of ['default', 'turbo']) { getSnapshotTestDescribe(variant)(`ReactRefreshLogBox ${variant}`, () => { let next: NextInstance beforeAll(async () => { next = await createNext({ files: {}, skipStart: true, }) }) afterAll(() => next.destroy()) test('should strip whitespace correctly with newline', async () => { const { session, cleanup } = await sandbox(next) await session.patch( 'index.js', ` export default function Page() { return ( <>

index page

{ throw new Error('idk') }}> click me ) } ` ) await session.evaluate(() => document.querySelector('a').click()) expect(await session.hasRedbox(true)).toBe(true) expect(await session.getRedboxSource()).toMatchSnapshot() 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) // We start here. await session.patch( 'index.js', ` import * as React from 'react'; class ClassDefault extends React.Component { render() { return

Default Export

; } } export default ClassDefault; ` ) expect( await session.evaluate(() => document.querySelector('h1').textContent) ).toBe('Default Export') // 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') class ClassDefault extends React.Component { render() { return

Default Export

; } } export default ClassDefault; ` ) expect(await session.hasRedbox(true)).toBe(true) expect(await session.getRedboxSource()).toMatchSnapshot() await cleanup() }) // 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', ` export default function FunctionDefault() { return

hello

} ` ) await session.patch( 'index.js', ` import FunctionDefault from './FunctionDefault.js' import * as React from 'react' class ErrorBoundary extends React.Component { constructor() { super() this.state = { hasError: false, error: null }; } static getDerivedStateFromError(error) { return { hasError: true, error }; } render() { if (this.state.hasError) { return this.props.fallback; } return this.props.children; } } function App() { return ( error}> ); } export default App; ` ) expect( await session.evaluate(() => document.querySelector('h2').textContent) ).toBe('hello') 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') 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) // 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` ) await cleanup() }) test('unterminated JSX', async () => { const { session, cleanup } = await sandbox(next) await session.patch( 'index.js', ` export default () => { return (

lol

) } ` ) expect(await session.hasRedbox(false)).toBe(false) await session.patch( 'index.js', ` export default () => { return (

lol

div ) } ` ) expect(await session.hasRedbox(true)).toBe(true) const source = await session.getRedboxSource() expect(source).toMatchSnapshot() await cleanup() }) // 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', ` export default function ClickCount() { return

hello

} ` ) await session.patch( 'index.js', ` import Child from './Child'; export default function Home() { return (
) } ` ) expect(await session.hasRedbox(false)).toBe(false) expect( await session.evaluate(() => document.querySelector('p').textContent) ).toBe('hello') await session.patch( 'Child.js', ` import { Component } from 'react'; export default class ClickCount extends Component { render() { throw new Error() } } ` ) expect(await session.hasRedbox(true)).toBe(true) expect(await session.getRedboxSource()).toMatchSnapshot() await session.patch( 'Child.js', ` import { Component } from 'react'; export default class ClickCount extends Component { render() { return

hello new

} } ` ) expect(await session.hasRedbox(false)).toBe(false) expect( await session.evaluate(() => document.querySelector('p').textContent) ).toBe('hello new') await cleanup() }) test('css syntax errors', async () => { const { session, cleanup } = await sandbox(next) await session.write('index.module.css', `.button {}`) await session.patch( 'index.js', ` import './index.module.css'; export default () => { return (

lol

) } ` ) expect(await session.hasRedbox(false)).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) await session.patch( 'index.js', ` import { useCallback } from 'react' export default function Index() { const boom = useCallback(() => { throw new Error('end http://nextjs.org') }, []) return (
) } ` ) expect(await session.hasRedbox(false)).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( () => ( 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() { const boom = useCallback(() => { throw new Error('http://nextjs.org start') }, []) return (
) } ` ) expect(await session.hasRedbox(false)).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( () => ( 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() { const boom = useCallback(() => { throw new Error('middle http://nextjs.org end') }, []) return (
) } ` ) expect(await session.hasRedbox(false)).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( () => ( 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() { const boom = useCallback(() => { throw new Error('multiple http://nextjs.org links http://example.com') }, []) return (
) } ` ) expect(await session.hasRedbox(false)).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 ) ).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 session.patch( 'index.js', ` import { useCallback } from 'react' export default function Index() { const boom = useCallback(() => { throw new Error('multiple http://nextjs.org links (http://example.com)') }, []) return (
) } ` ) expect(await session.hasRedbox(false)).toBe(false) await session.evaluate(() => document.querySelector('button').click()) expect(await session.hasRedbox(true)).toBe(true) const header5 = await session.getRedboxDescription() expect(header5).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 ) ).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) await session.patch( 'index.js', ` export default () => { throw {'a': 1, 'b': 'x'}; return (
hello
) } ` ) 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', ` export default () => { return (
hello
) } ` ) expect(await session.hasRedbox(false)).toBe(false) await session.patch( 'index.js', ` class Hello {} export default () => { throw Hello return (
hello
) } ` ) 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', ` export default () => { throw "string error" return (
hello
) } ` ) 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', ` export default () => { throw null return (
hello
) } ` ) expect(await session.hasRedbox(true)).toBe(true) expect(await session.getRedboxDescription()).toContain( `Error: A null error was thrown` ) await cleanup() }) }) }