diff --git a/packages/next/src/client/components/react-dev-overlay/hot-reloader-client.tsx b/packages/next/src/client/components/react-dev-overlay/hot-reloader-client.tsx index 679796e239624..1c33313651a54 100644 --- a/packages/next/src/client/components/react-dev-overlay/hot-reloader-client.tsx +++ b/packages/next/src/client/components/react-dev-overlay/hot-reloader-client.tsx @@ -209,7 +209,39 @@ function processMessage( router: ReturnType, dispatcher: Dispatcher ) { - const obj = JSON.parse(e.data) + let obj + try { + obj = JSON.parse(e.data) + } catch {} + + if (!obj || !('action' in obj)) { + return + } + + function handleErrors(errors: any[]) { + // "Massage" webpack messages. + const formatted = formatWebpackMessages({ + errors: errors, + warnings: [], + }) + + // Only show the first error. + dispatcher.onBuildError(formatted.errors[0]) + + // Also log them to the console. + for (let i = 0; i < formatted.errors.length; i++) { + console.error(stripAnsi(formatted.errors[i])) + } + + // Do not attempt to reload now. + // We will reload on next success instead. + if (process.env.__NEXT_TEST_MODE) { + if (self.__NEXT_HMR_CB) { + self.__NEXT_HMR_CB(formatted.errors[0]) + self.__NEXT_HMR_CB = null + } + } + } switch (obj.action) { case 'building': { @@ -239,28 +271,7 @@ function processMessage( }) ) - // "Massage" webpack messages. - let formatted = formatWebpackMessages({ - errors: errors, - warnings: [], - }) - - // Only show the first error. - dispatcher.onBuildError(formatted.errors[0]) - - // Also log them to the console. - for (let i = 0; i < formatted.errors.length; i++) { - console.error(stripAnsi(formatted.errors[i])) - } - - // Do not attempt to reload now. - // We will reload on next success instead. - if (process.env.__NEXT_TEST_MODE) { - if (self.__NEXT_HMR_CB) { - self.__NEXT_HMR_CB(formatted.errors[0]) - self.__NEXT_HMR_CB = null - } - } + handleErrors(errors) return } @@ -388,6 +399,16 @@ function processMessage( router.fastRefresh() return } + case 'serverError': { + const { errorJSON } = obj + if (errorJSON) { + const { message, stack } = JSON.parse(errorJSON) + const error = new Error(message) + error.stack = stack + handleErrors([error]) + } + return + } case 'pong': { const { invalid } = obj if (invalid) { @@ -468,18 +489,12 @@ export default function HotReload({ const router = useRouter() useEffect(() => { const handler = (event: MessageEvent) => { - if ( - !event.data.includes('action') && - // TODO-APP: clean this up for consistency - !event.data.includes('pong') - ) { - return - } - try { processMessage(event, sendMessage, router, dispatcher) - } catch (ex) { - console.warn('Invalid HMR message: ' + event.data + '\n', ex) + } catch (err: any) { + console.warn( + '[HMR] Invalid message: ' + event.data + '\n' + (err?.stack ?? '') + ) } } diff --git a/packages/next/src/client/dev/amp-dev.ts b/packages/next/src/client/dev/amp-dev.ts index 36916620b44c7..4eea82dbd0863 100644 --- a/packages/next/src/client/dev/amp-dev.ts +++ b/packages/next/src/client/dev/amp-dev.ts @@ -85,6 +85,8 @@ addMessageListener((event) => { try { const message = JSON.parse(event.data) + // action `serverError` is not for amp-dev + if (message.action === 'serverError') return if (message.action === 'sync' || message.action === 'built') { if (!message.hash) { return @@ -94,8 +96,10 @@ addMessageListener((event) => { } else if (message.action === 'reloadPage') { window.location.reload() } - } catch (ex) { - console.warn('Invalid HMR message: ' + event.data + '\n' + ex) + } catch (err: any) { + console.warn( + '[HMR] Invalid message: ' + event.data + '\n' + (err?.stack ?? '') + ) } }) diff --git a/packages/next/src/client/dev/error-overlay/hot-dev-client.ts b/packages/next/src/client/dev/error-overlay/hot-dev-client.ts index c1f1522142b43..031e670b47b0d 100644 --- a/packages/next/src/client/dev/error-overlay/hot-dev-client.ts +++ b/packages/next/src/client/dev/error-overlay/hot-dev-client.ts @@ -64,12 +64,15 @@ export default function connect() { register() addMessageListener((event) => { - if (!event.data.includes('action')) return - try { - processMessage(event) - } catch (ex) { - console.warn('Invalid HMR message: ' + event.data + '\n', ex) + const payload = JSON.parse(event.data) + if (!('action' in payload)) return + + processMessage(payload) + } catch (err: any) { + console.warn( + '[HMR] Invalid message: ' + event.data + '\n' + (err?.stack ?? '') + ) } }) @@ -226,8 +229,7 @@ function handleAvailableHash(hash: string) { } // Handle messages from the server. -function processMessage(e: any) { - const obj = JSON.parse(e.data) +function processMessage(obj: any) { switch (obj.action) { case 'building': { startLatency = Date.now() @@ -279,6 +281,16 @@ function processMessage(e: any) { window.location.reload() return } + case 'serverError': { + const { errorJSON } = obj + if (errorJSON) { + const { message, stack } = JSON.parse(errorJSON) + const error = new Error(message) + error.stack = stack + handleErrors([error]) + } + return + } default: { if (customHmrEventHandler) { customHmrEventHandler(obj) diff --git a/packages/next/src/client/dev/error-overlay/websocket.ts b/packages/next/src/client/dev/error-overlay/websocket.ts index f11ca45b657a8..725e7b80c37a6 100644 --- a/packages/next/src/client/dev/error-overlay/websocket.ts +++ b/packages/next/src/client/dev/error-overlay/websocket.ts @@ -26,7 +26,6 @@ export function connectHMR(options: { path: string assetPrefix: string timeout?: number - log?: boolean }) { if (!options.timeout) { options.timeout = 5 * 1000 @@ -36,7 +35,7 @@ export function connectHMR(options: { if (source) source.close() function handleOnline() { - if (options.log) console.log('[HMR] connected') + window.console.log('[HMR] connected') lastActivity = Date.now() } diff --git a/packages/next/src/client/dev/webpack-hot-middleware-client.ts b/packages/next/src/client/dev/webpack-hot-middleware-client.ts index 17a4b3d817800..91f14b488c39b 100644 --- a/packages/next/src/client/dev/webpack-hot-middleware-client.ts +++ b/packages/next/src/client/dev/webpack-hot-middleware-client.ts @@ -45,6 +45,9 @@ export default () => { } return } + if (obj.action === 'serverError') { + return + } throw new Error('Unexpected action ' + obj.action) }) diff --git a/packages/next/src/client/next-dev.ts b/packages/next/src/client/next-dev.ts index 12e7a7fe92a47..6a4a6c059d2e2 100644 --- a/packages/next/src/client/next-dev.ts +++ b/packages/next/src/client/next-dev.ts @@ -54,8 +54,19 @@ initialize({ webpackHMR }) let buildIndicatorHandler: any = () => {} - function devPagesManifestListener(event: any) { - if (event.data.includes('devPagesManifest')) { + function devPagesHmrListener(event: any) { + let payload + try { + payload = JSON.parse(event.data) + } catch {} + if (payload.event === 'server-error' && payload.errorJSON) { + const { stack, message } = JSON.parse(payload.errorJSON) + const error = new Error(message) + error.stack = stack + throw error + } else if (payload.action === 'reloadPage') { + window.location.reload() + } else if (payload.action === 'devPagesManifestUpdate') { fetch( `${assetPrefix}/_next/static/development/_devPagesManifest.json` ) @@ -66,10 +77,10 @@ initialize({ webpackHMR }) .catch((err) => { console.log(`Failed to fetch devPagesManifest`, err) }) - } else if (event.data.includes('middlewareChanges')) { + } else if (payload.event === 'middlewareChanges') { return window.location.reload() - } else if (event.data.includes('serverOnlyChanges')) { - const { pages } = JSON.parse(event.data) + } else if (payload.event === 'serverOnlyChanges') { + const { pages } = payload // Make sure to reload when the dev-overlay is showing for an // API route @@ -106,7 +117,7 @@ initialize({ webpackHMR }) } } } - addMessageListener(devPagesManifestListener) + addMessageListener(devPagesHmrListener) if (process.env.__NEXT_BUILD_INDICATOR) { initializeBuildWatcher((handler: any) => { diff --git a/packages/next/src/server/dev/hot-reloader.ts b/packages/next/src/server/dev/hot-reloader.ts index 0f0957b05e613..bbd856576ec50 100644 --- a/packages/next/src/server/dev/hot-reloader.ts +++ b/packages/next/src/server/dev/hot-reloader.ts @@ -185,6 +185,7 @@ export default class HotReloader { public edgeServerStats: webpack.Stats | null private clientError: Error | null = null private serverError: Error | null = null + private hmrServerError: Error | null = null private serverPrevDocumentHash: string | null private serverChunkNames?: Set private prevChunkNames?: Set @@ -327,10 +328,21 @@ export default class HotReloader { return { finished } } + public setHmrServerError(error: Error | null): void { + this.hmrServerError = error + } + + public clearHmrServerError(): void { + if (this.hmrServerError) { + this.setHmrServerError(null) + this.send('reloadPage') + } + } + public onHMR(req: IncomingMessage, _socket: Duplex, head: Buffer) { wsServer.handleUpgrade(req, req.socket, head, (client) => { this.webpackHotMiddleware?.onHMR(client) - this.onDemandEntries?.onHMR(client) + this.onDemandEntries?.onHMR(client, () => this.hmrServerError) client.addEventListener('message', ({ data }) => { data = typeof data !== 'string' ? data.toString() : data diff --git a/packages/next/src/server/dev/next-dev-server.ts b/packages/next/src/server/dev/next-dev-server.ts index e4005d3727959..b306c311fbd89 100644 --- a/packages/next/src/server/dev/next-dev-server.ts +++ b/packages/next/src/server/dev/next-dev-server.ts @@ -427,6 +427,7 @@ export default class DevServer extends Server { const fileWatchTimes = new Map() let enabledTypeScript = this.usingTypeScript let previousClientRouterFilters: any + let previousConflictingPagePaths: Set = new Set() wp.on('aggregated', async () => { let middlewareMatchers: MiddlewareMatcher[] | undefined @@ -440,6 +441,7 @@ export default class DevServer extends Server { let envChange = false let tsconfigChange = false + let conflictingPageChange = 0 devPageFiles.clear() @@ -598,20 +600,30 @@ export default class DevServer extends Server { } const numConflicting = conflictingAppPagePaths.size - if (numConflicting > 0) { - Log.error( - `Conflicting app and page file${ + conflictingPageChange = + numConflicting - previousConflictingPagePaths.size + + if (conflictingPageChange !== 0) { + if (numConflicting > 0) { + let errorMessage = `Conflicting app and page file${ numConflicting === 1 ? ' was' : 's were' - } found, please remove the conflicting files to continue:` - ) - for (const p of conflictingAppPagePaths) { - const appPath = relative(this.dir, appPageFilePaths.get(p)!) - const pagesPath = relative(this.dir, pagesPageFilePaths.get(p)!) - Log.error(` "${pagesPath}" - "${appPath}"`) + } found, please remove the conflicting files to continue:\n` + + for (const p of conflictingAppPagePaths) { + const appPath = relative(this.dir, appPageFilePaths.get(p)!) + const pagesPath = relative(this.dir, pagesPageFilePaths.get(p)!) + errorMessage += ` "${pagesPath}" - "${appPath}"\n` + } + this.hotReloader?.setHmrServerError(new Error(errorMessage)) + } else if (numConflicting === 0) { + await this.matchers.reload() + this.hotReloader?.clearHmrServerError() } } - let clientRouterFilters: any + previousConflictingPagePaths = conflictingAppPagePaths + + let clientRouterFilters: any if (this.nextConfig.experimental.clientRouterFilter) { clientRouterFilters = createClientRouterFilter( Object.keys(appPaths), @@ -802,7 +814,9 @@ export default class DevServer extends Server { !this.sortedRoutes?.every((val, idx) => val === sortedRoutes[idx]) ) { // emit the change so clients fetch the update - this.hotReloader?.send(undefined, { devPagesManifest: true }) + this.hotReloader?.send('devPagesManifestUpdate', { + devPagesManifest: true, + }) } this.sortedRoutes = sortedRoutes @@ -997,9 +1011,7 @@ export default class DevServer extends Server { ) } if (appFile && pagesFile) { - throw new Error( - `Conflicting app and page file found: "app${appFile}" and "pages${pagesFile}". Please remove one to continue.` - ) + return false } return Boolean(appFile || pagesFile) diff --git a/packages/next/src/server/dev/on-demand-entry-handler.ts b/packages/next/src/server/dev/on-demand-entry-handler.ts index 4f7985b6989c1..72977801abb2e 100644 --- a/packages/next/src/server/dev/on-demand-entry-handler.ts +++ b/packages/next/src/server/dev/on-demand-entry-handler.ts @@ -26,7 +26,7 @@ import { isMiddlewareFile, isMiddlewareFilename, } from '../../build/utils' -import { PageNotFoundError } from '../../shared/lib/utils' +import { PageNotFoundError, stringifyError } from '../../shared/lib/utils' import { CompilerNameValues, COMPILER_INDEXES, @@ -879,9 +879,28 @@ export function onDemandEntryHandler({ } }, - onHMR(client: ws) { + onHMR(client: ws, getHmrServerError: () => Error | null) { + let bufferedHmrServerError: Error | null = null + + client.addEventListener('close', () => { + bufferedHmrServerError = null + }) client.addEventListener('message', ({ data }) => { try { + const error = getHmrServerError() + + // New error occurred: buffered error is flushed and new error occurred + if (!bufferedHmrServerError && error) { + client.send( + JSON.stringify({ + event: 'server-error', // for pages dir + action: 'serverError', // for app dir + errorJSON: stringifyError(error), + }) + ) + bufferedHmrServerError = null + } + const parsedData = JSON.parse( typeof data !== 'string' ? data.toString() : data ) diff --git a/packages/next/src/server/future/route-matcher-managers/default-route-matcher-manager.ts b/packages/next/src/server/future/route-matcher-managers/default-route-matcher-manager.ts index 060457d6d1f31..74d62871a04fe 100644 --- a/packages/next/src/server/future/route-matcher-managers/default-route-matcher-manager.ts +++ b/packages/next/src/server/future/route-matcher-managers/default-route-matcher-manager.ts @@ -64,6 +64,8 @@ export class DefaultRouteMatcherManager implements RouteMatcherManager { const duplicates: Record = {} for (const providerMatchers of providersMatchers) { for (const matcher of providerMatchers) { + // Reset duplicated matches when reloading from pages conflicting state. + if (matcher.duplicated) delete matcher.duplicated // Test to see if the matcher being added is a duplicate. const duplicate = all.get(matcher.definition.pathname) if (duplicate) { diff --git a/packages/next/src/server/future/route-matcher-managers/dev-route-matcher-manager.ts b/packages/next/src/server/future/route-matcher-managers/dev-route-matcher-manager.ts index be1cdd87d2236..aff4389aabf83 100644 --- a/packages/next/src/server/future/route-matcher-managers/dev-route-matcher-manager.ts +++ b/packages/next/src/server/future/route-matcher-managers/dev-route-matcher-manager.ts @@ -55,19 +55,7 @@ export class DevRouteMatcherManager extends DefaultRouteMatcherManager { duplicate.definition.kind === RouteKind.PAGES_API ) ) { - throw new Error( - `Conflicting app and page file found: ${matcher.duplicated - // Sort the error output so that the app pages (starting with "app") - // are first. - .sort((a, b) => - a.definition.filename.localeCompare(b.definition.filename) - ) - .map( - (duplicate) => - `"${path.relative(this.dir, duplicate.definition.filename)}"` - ) - .join(' and ')}. Please remove one to continue.` - ) + return null } return match diff --git a/packages/next/src/shared/lib/utils.ts b/packages/next/src/shared/lib/utils.ts index 1468dfc06f2e0..f967be459022b 100644 --- a/packages/next/src/shared/lib/utils.ts +++ b/packages/next/src/shared/lib/utils.ts @@ -452,3 +452,7 @@ export interface CacheFs { mkdir(dir: string): Promise stat(f: string): Promise<{ mtime: Date }> } + +export function stringifyError(error: Error) { + return JSON.stringify({ message: error.message, stack: error.stack }) +} diff --git a/test/integration/conflicting-app-page-error/app/hello/page.js b/test/e2e/conflicting-app-page-error/app/another/page.js similarity index 54% rename from test/integration/conflicting-app-page-error/app/hello/page.js rename to test/e2e/conflicting-app-page-error/app/another/page.js index 279457a02ee11..cc99abeeb767e 100644 --- a/test/integration/conflicting-app-page-error/app/hello/page.js +++ b/test/e2e/conflicting-app-page-error/app/another/page.js @@ -1,3 +1,3 @@ export default function Page(props) { - return

hello app

+ return

{'another - app'}

} diff --git a/test/integration/conflicting-app-page-error/app/layout.js b/test/e2e/conflicting-app-page-error/app/layout.js similarity index 100% rename from test/integration/conflicting-app-page-error/app/layout.js rename to test/e2e/conflicting-app-page-error/app/layout.js diff --git a/test/integration/conflicting-app-page-error/app/non-conflict/page.js b/test/e2e/conflicting-app-page-error/app/non-conflict/page.js similarity index 100% rename from test/integration/conflicting-app-page-error/app/non-conflict/page.js rename to test/e2e/conflicting-app-page-error/app/non-conflict/page.js diff --git a/test/integration/conflicting-app-page-error/app/another/page.js b/test/e2e/conflicting-app-page-error/app/page.js similarity index 55% rename from test/integration/conflicting-app-page-error/app/another/page.js rename to test/e2e/conflicting-app-page-error/app/page.js index dc47e8019428f..d6dc5d531a37c 100644 --- a/test/integration/conflicting-app-page-error/app/another/page.js +++ b/test/e2e/conflicting-app-page-error/app/page.js @@ -1,3 +1,3 @@ export default function Page(props) { - return

another app

+ return

{`index - app`}

} diff --git a/test/e2e/conflicting-app-page-error/index.test.ts b/test/e2e/conflicting-app-page-error/index.test.ts new file mode 100644 index 0000000000000..de4f1aa561585 --- /dev/null +++ b/test/e2e/conflicting-app-page-error/index.test.ts @@ -0,0 +1,115 @@ +import { + getRedboxHeader, + hasRedbox, + check, + getRedboxSource, +} from 'next-test-utils' +import { createNextDescribe } from 'e2e-utils' + +createNextDescribe( + 'Conflict between app file and pages file', + { + files: __dirname, + skipDeployment: true, + skipStart: true, + }, + ({ next, isNextDev, isNextStart }) => { + if (isNextStart) { + it('should print error for conflicting app/page', async () => { + const { cliOutput } = await next.build() + expect(cliOutput).toMatch( + /Conflicting app and page files? (were|was) found/ + ) + + for (const [pagePath, appPath] of [ + ['pages/index.js', 'app/page.js'], + ['pages/another.js', 'app/another/page.js'], + ]) { + expect(cliOutput).toContain(`"${pagePath}" - "${appPath}"`) + } + + expect(cliOutput).not.toContain('/non-conflict-pages') + expect(cliOutput).not.toContain('/non-conflict') + }) + } + + async function containConflictsError(browser, conflicts) { + await check(async () => { + expect(await hasRedbox(browser, true)).toBe(true) + const redboxSource = await getRedboxSource(browser) + expect(redboxSource).toMatch( + /Conflicting app and page files? (were|was) found, please remove the conflicting files to continue:/ + ) + + for (const pair of conflicts) { + expect(redboxSource).toContain(`"${pair[0]}" - "${pair[1]}"`) + } + + return 'success' + }, /success/) + } + + if (isNextDev) { + it('should show error overlay for /another', async () => { + await next.start() + const browser = await next.browser('/another') + expect(await hasRedbox(browser, true)).toBe(true) + await containConflictsError(browser, [ + ['pages/index.js', 'app/page.js'], + ['pages/another.js', 'app/another/page.js'], + ]) + }) + + it('should show error overlay for /', async () => { + const browser = await next.browser('/') + expect(await hasRedbox(browser, true)).toBe(true) + await containConflictsError(browser, [ + ['pages/index.js', 'app/page.js'], + ['pages/another.js', 'app/another/page.js'], + ]) + }) + + it('should support hmr with conflicts', async () => { + const browser = await next.browser('/') + expect(await hasRedbox(browser, true)).toBe(true) + + await next.renameFile('pages/index.js', 'pages/index2.js') + await next.renameFile('pages/another.js', 'pages/another2.js') + + // Wait for successful recompilation + await browser.loadPage(next.url + '/') + expect(await hasRedbox(browser, false)).toBe(false) + expect(await browser.elementByCss('p').text()).toContain('index - app') + + await browser.loadPage(next.url + '/another') + expect(await browser.elementByCss('p').text()).toBe('another - app') + }) + + it('should not show error overlay for non conflict pages under app or pages dir', async () => { + const browser = await next.browser('/non-conflict') + expect(await hasRedbox(browser, false)).toBe(false) + expect(await getRedboxHeader(browser)).toBeUndefined() + expect(await browser.elementByCss('p').text()).toBe('non-conflict app') + + await browser.loadPage(next.url + '/non-conflict-pages') + expect(await hasRedbox(browser, false)).toBe(false) + expect(await getRedboxHeader(browser)).toBeUndefined() + expect(await browser.elementByCss('h1').text()).toBe( + 'non-conflict pages' + ) + }) + + it('should error again when there is new conflict', async () => { + const browser = await next.browser('/') + expect(await hasRedbox(browser, false)).toBe(false) + + // Re-trigger the conflicted errors + await next.renameFile('pages/index2.js', 'pages/index.js') + expect(await hasRedbox(browser, true)).toBe(true) + await containConflictsError(browser, [ + ['pages/index.js', 'app/page.js'], + ]) + }) + } + } +) diff --git a/test/integration/conflicting-app-page-error/pages/another.js b/test/e2e/conflicting-app-page-error/pages/another.js similarity index 52% rename from test/integration/conflicting-app-page-error/pages/another.js rename to test/e2e/conflicting-app-page-error/pages/another.js index ef95d4b1bb886..8714510ba39a3 100644 --- a/test/integration/conflicting-app-page-error/pages/another.js +++ b/test/e2e/conflicting-app-page-error/pages/another.js @@ -1,3 +1,3 @@ export default function Page(props) { - return

another page

+ return

{'another - pages'}

} diff --git a/test/integration/conflicting-app-page-error/pages/index.js b/test/e2e/conflicting-app-page-error/pages/index.js similarity index 54% rename from test/integration/conflicting-app-page-error/pages/index.js rename to test/e2e/conflicting-app-page-error/pages/index.js index 821fdd14d0972..ec7bda96f5100 100644 --- a/test/integration/conflicting-app-page-error/pages/index.js +++ b/test/e2e/conflicting-app-page-error/pages/index.js @@ -1,3 +1,3 @@ export default function Page(props) { - return

index page

+ return

{'index - pages'}

} diff --git a/test/e2e/conflicting-app-page-error/pages/non-conflict-pages.js b/test/e2e/conflicting-app-page-error/pages/non-conflict-pages.js new file mode 100644 index 0000000000000..96258da3c16ce --- /dev/null +++ b/test/e2e/conflicting-app-page-error/pages/non-conflict-pages.js @@ -0,0 +1,3 @@ +export default function Page() { + return

{`non-conflict pages`}

+} diff --git a/test/integration/conflicting-app-page-error/app/page.js b/test/integration/conflicting-app-page-error/app/page.js deleted file mode 100644 index 821fdd14d0972..0000000000000 --- a/test/integration/conflicting-app-page-error/app/page.js +++ /dev/null @@ -1,3 +0,0 @@ -export default function Page(props) { - return

index page

-} diff --git a/test/integration/conflicting-app-page-error/next.config.js b/test/integration/conflicting-app-page-error/next.config.js deleted file mode 100644 index 4ba52ba2c8df6..0000000000000 --- a/test/integration/conflicting-app-page-error/next.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {} diff --git a/test/integration/conflicting-app-page-error/pages/hello.js b/test/integration/conflicting-app-page-error/pages/hello.js deleted file mode 100644 index 1ea1be8989dda..0000000000000 --- a/test/integration/conflicting-app-page-error/pages/hello.js +++ /dev/null @@ -1,3 +0,0 @@ -export default function Page(props) { - return

hello page

-} diff --git a/test/integration/conflicting-app-page-error/pages/non-conflict-pages.js b/test/integration/conflicting-app-page-error/pages/non-conflict-pages.js deleted file mode 100644 index 86f3a93651b38..0000000000000 --- a/test/integration/conflicting-app-page-error/pages/non-conflict-pages.js +++ /dev/null @@ -1,3 +0,0 @@ -export default function Page() { - return

Hello World!

-} diff --git a/test/integration/conflicting-app-page-error/test/index.test.js b/test/integration/conflicting-app-page-error/test/index.test.js deleted file mode 100644 index 4df0373ba0726..0000000000000 --- a/test/integration/conflicting-app-page-error/test/index.test.js +++ /dev/null @@ -1,113 +0,0 @@ -/* eslint-env jest */ - -import path from 'path' -import { - killApp, - findPort, - getRedboxHeader, - hasRedbox, - launchApp, - nextBuild, - waitFor, - check, -} from 'next-test-utils' -import webdriver from 'next-webdriver' - -let app -let appPort -const appDir = path.join(__dirname, '..') -let output = '' - -function runTests({ dev }) { - it('should print error for conflicting app/page', async () => { - await check(() => output, /Conflicting app and page files were found/) - - for (const [pagePath, appPath] of [ - ['pages/index.js', 'app/page.js'], - ['pages/hello.js', 'app/hello/page.js'], - ['pages/another.js', 'app/another/page.js'], - ]) { - expect(output).toContain(`"${pagePath}" - "${appPath}"`) - } - expect(output).not.toContain('/non-conflict-pages') - expect(output).not.toContain('/non-conflict') - }) - - if (dev) { - it('should show error overlay for /', async () => { - const browser = await webdriver(appPort, '/') - expect(await hasRedbox(browser, true)).toBe(true) - expect(await getRedboxHeader(browser)).toContain( - 'Conflicting app and page file found: "app/page.js" and "pages/index.js". Please remove one to continue.' - ) - }) - - it('should show error overlay for /hello', async () => { - const browser = await webdriver(appPort, '/hello') - expect(await hasRedbox(browser, true)).toBe(true) - expect(await getRedboxHeader(browser)).toContain( - 'Conflicting app and page file found: "app/hello/page.js" and "pages/hello.js". Please remove one to continue.' - ) - }) - - it('should show error overlay for /another', async () => { - const browser = await webdriver(appPort, '/another') - expect(await hasRedbox(browser, true)).toBe(true) - expect(await getRedboxHeader(browser)).toContain( - 'Conflicting app and page file found: "app/another/page.js" and "pages/another.js". Please remove one to continue.' - ) - }) - - it('should not show error overlay for /non-conflict-pages', async () => { - const browser = await webdriver(appPort, '/non-conflict-pages') - expect(await hasRedbox(browser, false)).toBe(false) - expect(await getRedboxHeader(browser)).toBe(null) - expect(await browser.elementByCss('h1').text()).toBe('Hello World!') - }) - - it('should not show error overlay for /non-conflict', async () => { - const browser = await webdriver(appPort, '/non-conflict') - expect(await hasRedbox(browser, false)).toBe(false) - expect(await getRedboxHeader(browser)).toBe(null) - expect(await browser.elementByCss('p').text()).toBe('non-conflict app') - }) - } -} - -describe('Conflict between app file and page file', () => { - describe('next dev', () => { - beforeAll(async () => { - output = '' - appPort = await findPort() - app = await launchApp(appDir, appPort, { - onStdout(msg) { - output += msg || '' - }, - onStderr(msg) { - output += msg || '' - }, - }) - await waitFor(800) - }) - afterAll(() => { - killApp(app) - }) - runTests({ dev: true }) - }) - - describe('next build', () => { - beforeAll(async () => { - output = '' - const { stdout, stderr } = await nextBuild(appDir, [], { - stdout: true, - stderr: true, - env: { NEXT_SKIP_APP_REACT_INSTALL: '1' }, - }) - output = stdout + stderr - }) - afterAll(() => { - killApp(app) - }) - runTests({ dev: false }) - }) -}) diff --git a/test/lib/next-modes/next-start.ts b/test/lib/next-modes/next-start.ts index 279b261cd6b4e..942270f626e02 100644 --- a/test/lib/next-modes/next-start.ts +++ b/test/lib/next-modes/next-start.ts @@ -138,6 +138,18 @@ export class NextStartInstance extends NextInstance { } public async build() { + this.spawnOpts = { + cwd: this.testDir, + stdio: ['ignore', 'pipe', 'pipe'], + shell: false, + env: { + ...process.env, + ...this.env, + NODE_ENV: '' as any, + PORT: this.forcedPort || '0', + __NEXT_TEST_MODE: 'e2e', + }, + } return new Promise((resolve) => { const curOutput = this._cliOutput.length const exportArgs = ['pnpm', 'next', 'build'] diff --git a/test/lib/next-test-utils.js b/test/lib/next-test-utils.js index 93ada0b7cdd3c..edae6c35e0ae7 100644 --- a/test/lib/next-test-utils.js +++ b/test/lib/next-test-utils.js @@ -693,8 +693,9 @@ export async function getRedboxHeader(browser) { .find((p) => p.shadowRoot.querySelector('[data-nextjs-turbo-dialog-body]') ) - const root = portal.shadowRoot - return root.querySelector('[data-nextjs-turbo-dialog-body]').innerText + const root = portal?.shadowRoot + return root?.querySelector('[data-nextjs-turbo-dialog-body]') + ?.innerText }) } else { return evaluate(browser, () => { @@ -703,8 +704,8 @@ export async function getRedboxHeader(browser) { .find((p) => p.shadowRoot.querySelector('[data-nextjs-dialog-header]') ) - const root = portal.shadowRoot - return root.querySelector('[data-nextjs-dialog-header]').innerText + const root = portal?.shadowRoot + return root?.querySelector('[data-nextjs-dialog-header]')?.innerText }) } },