@@ -322,7 +444,7 @@ describe('using a single matcher with i18n and basePath', () => {
props: { message: \`(\${locale}) Hello from /\` }
})
`,
- 'pages/[...route].js': `
+ 'pages/[...route].js': `
export default function Page({ message }) {
return
catchall page
@@ -333,7 +455,7 @@ describe('using a single matcher with i18n and basePath', () => {
props: { message: \`(\${locale}) Hello from /\` + params.route.join("/") }
})
`,
- 'middleware.js': `
+ 'middleware.js': `
import { NextResponse } from 'next/server'
export const config = { matcher: '/' };
export default (req) => {
@@ -342,8 +464,9 @@ describe('using a single matcher with i18n and basePath', () => {
return res;
}
`,
- 'next.config.js': `
+ 'next.config.js': `
module.exports = {
+ ${trailingSlash ? 'trailingSlash: true,' : ''}
basePath: '/root',
i18n: {
localeDetection: false,
@@ -352,47 +475,57 @@ describe('using a single matcher with i18n and basePath', () => {
}
}
`,
- },
- dependencies: {},
+ },
+ dependencies: {},
+ })
+ })
+ afterAll(() => next.destroy())
+
+ it(`adds the header for a matched path`, async () => {
+ const res1 = await fetchViaHTTP(next.url, `/root`)
+ expect(await res1.text()).toContain(`(en) Hello from /`)
+ expect(res1.headers.get('X-From-Middleware')).toBe('true')
+ const res2 = await fetchViaHTTP(next.url, `/root/es`)
+ expect(await res2.text()).toContain(`(es) Hello from /`)
+ expect(res2.headers.get('X-From-Middleware')).toBe('true')
})
- })
- afterAll(() => next.destroy())
-
- it(`adds the header for a matched path`, async () => {
- const res1 = await fetchViaHTTP(next.url, `/root`)
- expect(await res1.text()).toContain(`(en) Hello from /`)
- expect(res1.headers.get('X-From-Middleware')).toBe('true')
- const res2 = await fetchViaHTTP(next.url, `/root/es`)
- expect(await res2.text()).toContain(`(es) Hello from /`)
- expect(res2.headers.get('X-From-Middleware')).toBe('true')
- })
- it(`adds the headers for a matched data path`, async () => {
- const res1 = await fetchViaHTTP(
- next.url,
- `/root/_next/data/${next.buildId}/en.json`,
- undefined,
- { headers: { 'x-nextjs-data': '1' } }
- )
- expect(await res1.json()).toMatchObject({
- pageProps: { message: `(en) Hello from /` },
+ it('adds the header for a mathed root path with /index', async () => {
+ const res1 = await fetchViaHTTP(next.url, `/root/index`)
+ expect(await res1.text()).toContain(`(en) Hello from /`)
+ expect(res1.headers.get('X-From-Middleware')).toBe('true')
+ const res2 = await fetchViaHTTP(next.url, `/root/es/index`)
+ expect(await res2.text()).toContain(`(es) Hello from /`)
+ expect(res2.headers.get('X-From-Middleware')).toBe('true')
})
- expect(res1.headers.get('X-From-Middleware')).toBe('true')
- const res2 = await fetchViaHTTP(
- next.url,
- `/root/_next/data/${next.buildId}/es.json`,
- undefined,
- { headers: { 'x-nextjs-data': '1' } }
- )
- expect(await res2.json()).toMatchObject({
- pageProps: { message: `(es) Hello from /` },
+
+ it(`adds the headers for a matched data path`, async () => {
+ const res1 = await fetchViaHTTP(
+ next.url,
+ `/root/_next/data/${next.buildId}/en.json`,
+ undefined,
+ { headers: { 'x-nextjs-data': '1' } }
+ )
+ expect(await res1.json()).toMatchObject({
+ pageProps: { message: `(en) Hello from /` },
+ })
+ expect(res1.headers.get('X-From-Middleware')).toBe('true')
+ const res2 = await fetchViaHTTP(
+ next.url,
+ `/root/_next/data/${next.buildId}/es.json`,
+ undefined,
+ { headers: { 'x-nextjs-data': '1' } }
+ )
+ expect(await res2.json()).toMatchObject({
+ pageProps: { message: `(es) Hello from /` },
+ })
+ expect(res2.headers.get('X-From-Middleware')).toBe('true')
})
- expect(res2.headers.get('X-From-Middleware')).toBe('true')
- })
- it(`does not add the header for an unmatched path`, async () => {
- const response = await fetchViaHTTP(next.url, `/root/about/me`)
- expect(await response.text()).toContain('Hello from /about/me')
- expect(response.headers.get('X-From-Middleware')).toBeNull()
- })
-})
+ it(`does not add the header for an unmatched path`, async () => {
+ const response = await fetchViaHTTP(next.url, `/root/about/me`)
+ expect(await response.text()).toContain('Hello from /about/me')
+ expect(response.headers.get('X-From-Middleware')).toBeNull()
+ })
+ }
+)
diff --git a/test/integration/custom-routes/next.config.js b/test/integration/custom-routes/next.config.js
index 3f9fc9bfcd23436..1a06c9778588799 100644
--- a/test/integration/custom-routes/next.config.js
+++ b/test/integration/custom-routes/next.config.js
@@ -12,6 +12,11 @@ module.exports = {
},
]
: []),
+ {
+ source: '/to-websocket',
+ destination:
+ 'http://localhost:__EXTERNAL_PORT__/_next/webpack-hmr?page=/about',
+ },
{
source: '/to-nowhere',
destination: 'http://localhost:12233',
diff --git a/test/integration/custom-routes/test/index.test.js b/test/integration/custom-routes/test/index.test.js
index 55b60e85546a986..cd5fc57f5ddb15f 100644
--- a/test/integration/custom-routes/test/index.test.js
+++ b/test/integration/custom-routes/test/index.test.js
@@ -5,6 +5,7 @@ import url from 'url'
import stripAnsi from 'strip-ansi'
import fs from 'fs-extra'
import { join } from 'path'
+import WebSocket from 'ws'
import cheerio from 'cheerio'
import webdriver from 'next-webdriver'
import escapeRegex from 'escape-string-regexp'
@@ -39,6 +40,29 @@ let appPort
let app
const runTests = (isDev = false) => {
+ it('should successfully rewrite a WebSocket request', async () => {
+ const messages = []
+ const ws = await new Promise((resolve, reject) => {
+ let socket = new WebSocket(`ws://localhost:${appPort}/to-websocket`)
+ socket.on('message', (data) => {
+ messages.push(data.toString())
+ })
+ socket.on('open', () => resolve(socket))
+ socket.on('error', (err) => {
+ console.error(err)
+ socket.close()
+ reject()
+ })
+ })
+
+ await check(
+ () => (messages.length > 0 ? 'success' : JSON.stringify(messages)),
+ 'success'
+ )
+ ws.close()
+ expect([...externalServerHits]).toEqual(['/_next/webpack-hmr?page=/about'])
+ })
+
it('should not rewrite for _next/data route when a match is found', async () => {
const initial = await fetchViaHTTP(appPort, '/overridden/first')
expect(initial.status).toBe(200)
@@ -1809,6 +1833,11 @@ const runTests = (isDev = false) => {
},
],
afterFiles: [
+ {
+ destination: `http://localhost:${externalServerPort}/_next/webpack-hmr?page=/about`,
+ regex: normalizeRegEx('^\\/to-websocket(?:\\/)?$'),
+ source: '/to-websocket',
+ },
{
destination: 'http://localhost:12233',
regex: normalizeRegEx('^\\/to-nowhere(?:\\/)?$'),
@@ -2235,6 +2264,14 @@ describe('Custom routes', () => {
const externalHost = req.headers['host']
res.end(`hi ${nextHost} from ${externalHost}`)
})
+ const wsServer = new WebSocket.Server({ noServer: true })
+
+ externalServer.on('upgrade', (req, socket, head) => {
+ externalServerHits.add(req.url)
+ wsServer.handleUpgrade(req, socket, head, (client) => {
+ client.send('hello world')
+ })
+ })
await new Promise((resolve, reject) => {
externalServer.listen(externalServerPort, (error) => {
if (error) return reject(error)
@@ -2244,7 +2281,7 @@ describe('Custom routes', () => {
nextConfigRestoreContent = await fs.readFile(nextConfigPath, 'utf8')
await fs.writeFile(
nextConfigPath,
- nextConfigRestoreContent.replace(/__EXTERNAL_PORT__/, externalServerPort)
+ nextConfigRestoreContent.replace(/__EXTERNAL_PORT__/g, externalServerPort)
)
})
afterAll(async () => {
diff --git a/test/integration/env-config/app/pages/another-global.js b/test/integration/env-config/app/pages/another-global.js
new file mode 100644
index 000000000000000..7db38a7029f9e09
--- /dev/null
+++ b/test/integration/env-config/app/pages/another-global.js
@@ -0,0 +1 @@
+export default () =>
{process.env.NEXT_PUBLIC_HELLO_WORLD}
diff --git a/test/integration/env-config/app/pages/api/all.js b/test/integration/env-config/app/pages/api/all.js
index 882343cf27b4a71..52f181fe5fb8304 100644
--- a/test/integration/env-config/app/pages/api/all.js
+++ b/test/integration/env-config/app/pages/api/all.js
@@ -21,18 +21,22 @@ const variables = [
'ENV_FILE_EXPANDED_CONCAT',
'ENV_FILE_EXPANDED_ESCAPED',
'ENV_FILE_KEY_EXCLAMATION',
+ 'NEW_ENV_KEY',
+ 'NEW_ENV_LOCAL_KEY',
+ 'NEW_ENV_DEV_KEY',
+ 'NEXT_PUBLIC_HELLO_WORLD',
]
-const items = {
- nextConfigEnv: process.env.nextConfigEnv,
- nextConfigPublicEnv: process.env.nextConfigPublicEnv,
-}
+export default async (req, res) => {
+ const items = {
+ nextConfigEnv: process.env.nextConfigEnv,
+ nextConfigPublicEnv: process.env.nextConfigPublicEnv,
+ }
-variables.forEach((variable) => {
- items[variable] = process.env[variable]
-})
+ variables.forEach((variable) => {
+ items[variable] = process.env[variable]
+ })
-export default async (req, res) => {
// Only for testing, don't do this...
res.json(items)
}
diff --git a/test/integration/env-config/app/pages/index.js b/test/integration/env-config/app/pages/index.js
index 8e0d187a75f8334..c8bc8ce3a77e03d 100644
--- a/test/integration/env-config/app/pages/index.js
+++ b/test/integration/env-config/app/pages/index.js
@@ -21,6 +21,10 @@ const variables = [
'ENV_FILE_EXPANDED_CONCAT',
'ENV_FILE_EXPANDED_ESCAPED',
'ENV_FILE_KEY_EXCLAMATION',
+ 'NEW_ENV_KEY',
+ 'NEW_ENV_LOCAL_KEY',
+ 'NEW_ENV_DEV_KEY',
+ 'NEXT_PUBLIC_HELLO_WORLD',
]
export async function getStaticProps() {
diff --git a/test/integration/env-config/app/pages/some-ssg.js b/test/integration/env-config/app/pages/some-ssg.js
index 5486f5de7b405dc..d46d371b59065a4 100644
--- a/test/integration/env-config/app/pages/some-ssg.js
+++ b/test/integration/env-config/app/pages/some-ssg.js
@@ -21,6 +21,10 @@ const variables = [
'ENV_FILE_EXPANDED_CONCAT',
'ENV_FILE_EXPANDED_ESCAPED',
'ENV_FILE_KEY_EXCLAMATION',
+ 'NEW_ENV_KEY',
+ 'NEW_ENV_LOCAL_KEY',
+ 'NEW_ENV_DEV_KEY',
+ 'NEXT_PUBLIC_HELLO_WORLD',
]
export async function getStaticProps() {
diff --git a/test/integration/env-config/app/pages/some-ssp.js b/test/integration/env-config/app/pages/some-ssp.js
index 0be53f7d03786e2..ee985c926e2281c 100644
--- a/test/integration/env-config/app/pages/some-ssp.js
+++ b/test/integration/env-config/app/pages/some-ssp.js
@@ -21,6 +21,10 @@ const variables = [
'ENV_FILE_EXPANDED_CONCAT',
'ENV_FILE_EXPANDED_ESCAPED',
'ENV_FILE_KEY_EXCLAMATION',
+ 'NEW_ENV_KEY',
+ 'NEW_ENV_LOCAL_KEY',
+ 'NEW_ENV_DEV_KEY',
+ 'NEXT_PUBLIC_HELLO_WORLD',
]
export async function getServerSideProps() {
diff --git a/test/integration/env-config/test/index.test.js b/test/integration/env-config/test/index.test.js
index c87ce54f6f3b209..23baf525c59f084 100644
--- a/test/integration/env-config/test/index.test.js
+++ b/test/integration/env-config/test/index.test.js
@@ -12,10 +12,12 @@ import {
launchApp,
killApp,
fetchViaHTTP,
+ check,
} from 'next-test-utils'
let app
let appPort
+let output = ''
const appDir = join(__dirname, '../app')
const getEnvFromHtml = async (path) => {
@@ -27,7 +29,7 @@ const getEnvFromHtml = async (path) => {
return env
}
-const runTests = (mode = 'dev') => {
+const runTests = (mode = 'dev', didReload = false) => {
const isDevOnly = mode === 'dev'
const isTestEnv = mode === 'test'
const isDev = isDevOnly || isTestEnv
@@ -56,6 +58,11 @@ const runTests = (mode = 'dev') => {
expect(data.ENV_FILE_EMPTY_FIRST).toBe(isTestEnv ? '' : '$escaped')
expect(data.ENV_FILE_PROCESS_ENV).toBe('env-cli')
+ if (didReload) {
+ expect(data.NEW_ENV_KEY).toBe('true')
+ expect(data.NEW_ENV_LOCAL_KEY).toBe('hello')
+ expect(data.NEW_ENV_DEV_KEY).toBe('from-dev')
+ }
expect(data.nextConfigEnv).toBe('hello from next.config.js')
expect(data.nextConfigPublicEnv).toBe('hello again from next.config.js')
}
@@ -138,17 +145,84 @@ const runTests = (mode = 'dev') => {
describe('Env Config', () => {
describe('dev mode', () => {
beforeAll(async () => {
+ output = ''
appPort = await findPort()
app = await launchApp(appDir, appPort, {
env: {
PROCESS_ENV_KEY: 'processenvironment',
ENV_FILE_PROCESS_ENV: 'env-cli',
},
+ onStdout(msg) {
+ output += msg || ''
+ },
+ onStderr(msg) {
+ output += msg || ''
+ },
})
+
+ await renderViaHTTP(appPort, '/another-global')
})
afterAll(() => killApp(app))
runTests('dev')
+
+ describe('with hot reload', () => {
+ const originalContents = []
+ beforeAll(async () => {
+ const outputIndex = output.length
+ const envToUpdate = [
+ {
+ toAdd: 'NEW_ENV_KEY=true',
+ file: '.env',
+ },
+ {
+ toAdd: 'NEW_ENV_LOCAL_KEY=hello',
+ file: '.env.local',
+ },
+ {
+ toAdd: 'NEW_ENV_DEV_KEY=from-dev\nNEXT_PUBLIC_HELLO_WORLD=again',
+ file: '.env.development',
+ },
+ ]
+
+ for (const { file, toAdd } of envToUpdate) {
+ const content = await fs.readFile(join(appDir, file), 'utf8')
+ originalContents.push({ file, content })
+ await fs.writeFile(join(appDir, file), content + '\n' + toAdd)
+ }
+
+ await check(() => {
+ return output.substring(outputIndex)
+ }, /Loaded env from/)
+ })
+ afterAll(async () => {
+ for (const { file, content } of originalContents) {
+ await fs.writeFile(join(appDir, file), content)
+ }
+ })
+
+ runTests('dev', true)
+
+ it('should update inlined values correctly', async () => {
+ await renderViaHTTP(appPort, '/another-global')
+
+ const buildManifest = await fs.readJson(
+ join(__dirname, '../app/.next/build-manifest.json')
+ )
+
+ const pageFile = buildManifest.pages['/another-global'].find(
+ (filename) => filename.includes('pages/another-global')
+ )
+
+ // read client bundle contents since a server side render can
+ // have the value available during render but it not be injected
+ const bundleContent = await fs.readFile(
+ join(appDir, '.next', pageFile),
+ 'utf8'
+ )
+ expect(bundleContent).toContain('again')
+ })
+ })
})
describe('test environment', () => {
diff --git a/test/integration/image-future/base-path/test/static.test.js b/test/integration/image-future/base-path/test/static.test.js
index 077fab76c9e9f29..0a55ccc0d9a50fc 100644
--- a/test/integration/image-future/base-path/test/static.test.js
+++ b/test/integration/image-future/base-path/test/static.test.js
@@ -65,7 +65,7 @@ const runTests = () => {
})
it('Should add a blur to a statically imported image', async () => {
expect(html).toContain(
- `style="background-size:cover;background-position:0% 0%;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 300'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='discrete' tableValues='1 1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoKCgoKCgsMDAsPEA4QDxYUExMUFiIYGhgaGCIzICUgICUgMy03LCksNy1RQDg4QFFeT0pPXnFlZXGPiI+7u/sBCgoKCgoKCwwMCw8QDhAPFhQTExQWIhgaGBoYIjMgJSAgJSAzLTcsKSw3LVFAODhAUV5PSk9ecWVlcY+Ij7u7+//CABEIAAYACAMBIgACEQEDEQH/xAAnAAEBAAAAAAAAAAAAAAAAAAAABwEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAAmgP/xAAcEAACAQUBAAAAAAAAAAAAAAASFBMAAQMFERX/2gAIAQEAAT8AZ1HjrKZX55JysIc4Ff/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Af//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Af//Z'/%3E%3C/svg%3E")`
+ `style="background-size:cover;background-position:0% 0%;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 300'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoKCgoKCgsMDAsPEA4QDxYUExMUFiIYGhgaGCIzICUgICUgMy03LCksNy1RQDg4QFFeT0pPXnFlZXGPiI+7u/sBCgoKCgoKCwwMCw8QDhAPFhQTExQWIhgaGBoYIjMgJSAgJSAzLTcsKSw3LVFAODhAUV5PSk9ecWVlcY+Ij7u7+//CABEIAAYACAMBIgACEQEDEQH/xAAnAAEBAAAAAAAAAAAAAAAAAAAABwEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAAmgP/xAAcEAACAQUBAAAAAAAAAAAAAAASFBMAAQMFERX/2gAIAQEAAT8AZ1HjrKZX55JysIc4Ff/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Af//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Af//Z'/%3E%3C/svg%3E")`
)
})
}
diff --git a/test/integration/image-future/default/test/index.test.js b/test/integration/image-future/default/test/index.test.js
index fb800d9a36d82e9..1a9715f4f40cee4 100644
--- a/test/integration/image-future/default/test/index.test.js
+++ b/test/integration/image-future/default/test/index.test.js
@@ -1053,11 +1053,11 @@ function runTests(mode) {
$html('noscript > img').attr('id', 'unused')
expect($html('#blurry-placeholder-raw')[0].attribs.style).toContain(
- `background-size:cover;background-position:0% 0%;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 400'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='discrete' tableValues='1 1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8P4nhDwAGuAKPn6cicwAAAABJRU5ErkJggg=='/%3E%3C/svg%3E")`
+ `background-size:cover;background-position:0% 0%;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 400'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8P4nhDwAGuAKPn6cicwAAAABJRU5ErkJggg=='/%3E%3C/svg%3E")`
)
expect($html('#blurry-placeholder-with-lazy')[0].attribs.style).toContain(
- `background-size:cover;background-position:0% 0%;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 400'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='discrete' tableValues='1 1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mO0/8/wBwAE/wI85bEJ6gAAAABJRU5ErkJggg=='/%3E%3C/svg%3E")`
+ `background-size:cover;background-position:0% 0%;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 400'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mO0/8/wBwAE/wI85bEJ6gAAAABJRU5ErkJggg=='/%3E%3C/svg%3E")`
)
})
@@ -1088,7 +1088,7 @@ function runTests(mode) {
'background-image'
)
).toBe(
- `url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 400'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='discrete' tableValues='1 1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mO0/8/wBwAE/wI85bEJ6gAAAABJRU5ErkJggg=='/%3E%3C/svg%3E")`
+ `url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 400'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mO0/8/wBwAE/wI85bEJ6gAAAABJRU5ErkJggg=='/%3E%3C/svg%3E")`
)
await browser.eval('document.getElementById("spacer").remove()')
diff --git a/test/integration/image-future/default/test/static.test.js b/test/integration/image-future/default/test/static.test.js
index 11b8df8e0dbce41..7e02bf407b2b8d0 100644
--- a/test/integration/image-future/default/test/static.test.js
+++ b/test/integration/image-future/default/test/static.test.js
@@ -65,7 +65,7 @@ const runTests = () => {
})
it('Should add a blur to a statically imported image in "raw" mode', async () => {
expect(html).toContain(
- `style="background-size:cover;background-position:0% 0%;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 300'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='discrete' tableValues='1 1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoKCgoKCgsMDAsPEA4QDxYUExMUFiIYGhgaGCIzICUgICUgMy03LCksNy1RQDg4QFFeT0pPXnFlZXGPiI+7u/sBCgoKCgoKCwwMCw8QDhAPFhQTExQWIhgaGBoYIjMgJSAgJSAzLTcsKSw3LVFAODhAUV5PSk9ecWVlcY+Ij7u7+//CABEIAAYACAMBIgACEQEDEQH/xAAnAAEBAAAAAAAAAAAAAAAAAAAABwEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAAmgP/xAAcEAACAQUBAAAAAAAAAAAAAAASFBMAAQMFERX/2gAIAQEAAT8AZ1HjrKZX55JysIc4Ff/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Af//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Af//Z'/%3E%3C/svg%3E")`
+ `style="background-size:cover;background-position:0% 0%;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 300'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoKCgoKCgsMDAsPEA4QDxYUExMUFiIYGhgaGCIzICUgICUgMy03LCksNy1RQDg4QFFeT0pPXnFlZXGPiI+7u/sBCgoKCgoKCwwMCw8QDhAPFhQTExQWIhgaGBoYIjMgJSAgJSAzLTcsKSw3LVFAODhAUV5PSk9ecWVlcY+Ij7u7+//CABEIAAYACAMBIgACEQEDEQH/xAAnAAEBAAAAAAAAAAAAAAAAAAAABwEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAAmgP/xAAcEAACAQUBAAAAAAAAAAAAAAASFBMAAQMFERX/2gAIAQEAAT8AZ1HjrKZX55JysIc4Ff/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Af//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Af//Z'/%3E%3C/svg%3E")`
)
})
}
diff --git a/test/lib/next-modes/base.ts b/test/lib/next-modes/base.ts
index f6d2d9302260f47..2e9b7432f9cc418 100644
--- a/test/lib/next-modes/base.ts
+++ b/test/lib/next-modes/base.ts
@@ -178,10 +178,14 @@ export class NextInstance {
}
}
- const nextConfigFile = Object.keys(this.files).find((file) =>
+ let nextConfigFile = Object.keys(this.files).find((file) =>
file.startsWith('next.config.')
)
+ if (await fs.pathExists(path.join(this.testDir, 'next.config.js'))) {
+ nextConfigFile = 'next.config.js'
+ }
+
if (nextConfigFile && this.nextConfig) {
throw new Error(
`nextConfig provided on "createNext()" and as a file "${nextConfigFile}", use one or the other to continue`
diff --git a/test/production/fatal-render-errror/app/pages/_app.js b/test/production/fatal-render-errror/app/pages/_app.js
new file mode 100644
index 000000000000000..904b7838ceebfcb
--- /dev/null
+++ b/test/production/fatal-render-errror/app/pages/_app.js
@@ -0,0 +1,15 @@
+export default function App({ Component, pageProps }) {
+ if (process.env.NODE_ENV === 'production' && typeof window !== 'undefined') {
+ if (!window.renderAttempts) {
+ window.renderAttempts = 0
+ }
+ window.renderAttempts++
+ throw new Error('error in custom _app')
+ }
+ return (
+ <>
+
from _app
+
+ >
+ )
+}
diff --git a/test/production/fatal-render-errror/app/pages/_error.js b/test/production/fatal-render-errror/app/pages/_error.js
new file mode 100644
index 000000000000000..9b8ff8ee05875eb
--- /dev/null
+++ b/test/production/fatal-render-errror/app/pages/_error.js
@@ -0,0 +1,6 @@
+export default function Error() {
+ if (process.env.NODE_ENV === 'production' && typeof window !== 'undefined') {
+ throw new Error('error in custom _app')
+ }
+ return
Error encountered!
+}
diff --git a/test/production/fatal-render-errror/app/pages/index.js b/test/production/fatal-render-errror/app/pages/index.js
new file mode 100644
index 000000000000000..08263e34c35fd22
--- /dev/null
+++ b/test/production/fatal-render-errror/app/pages/index.js
@@ -0,0 +1,3 @@
+export default function Page() {
+ return
index page
+}
diff --git a/test/production/fatal-render-errror/app/pages/with-error.js b/test/production/fatal-render-errror/app/pages/with-error.js
new file mode 100644
index 000000000000000..2a240431ba18880
--- /dev/null
+++ b/test/production/fatal-render-errror/app/pages/with-error.js
@@ -0,0 +1,6 @@
+export default function Error() {
+ if (process.env.NODE_ENV === 'production' && typeof window !== 'undefined') {
+ throw new Error('error in pages/with-error')
+ }
+ return
with-error
+}
diff --git a/test/production/fatal-render-errror/index.test.ts b/test/production/fatal-render-errror/index.test.ts
new file mode 100644
index 000000000000000..1037cda593f20ef
--- /dev/null
+++ b/test/production/fatal-render-errror/index.test.ts
@@ -0,0 +1,55 @@
+import { createNext, FileRef } from 'e2e-utils'
+import { NextInstance } from 'test/lib/next-modes/base'
+import { check, renderViaHTTP, waitFor } from 'next-test-utils'
+import webdriver from 'next-webdriver'
+import { join } from 'path'
+
+describe('fatal-render-errror', () => {
+ let next: NextInstance
+
+ beforeAll(async () => {
+ next = await createNext({
+ files: new FileRef(join(__dirname, 'app')),
+ dependencies: {},
+ })
+ })
+ afterAll(() => next.destroy())
+
+ it('should render page without error correctly', async () => {
+ const html = await renderViaHTTP(next.url, '/')
+ expect(html).toContain('index page')
+ expect(html).toContain('from _app')
+ })
+
+ it('should handle fatal error in _app and _error without loop on direct visit', async () => {
+ const browser = await webdriver(next.url, '/with-error')
+
+ // wait a bit to see if we are rendering multiple times unexpectedly
+ await waitFor(500)
+ expect(await browser.eval('window.renderAttempts')).toBeLessThan(10)
+
+ const html = await browser.eval('document.documentElement.innerHTML')
+ expect(html).not.toContain('from _app')
+ expect(html).toContain(
+ 'Application error: a client-side exception has occurred'
+ )
+ })
+
+ it('should handle fatal error in _app and _error without loop on client-transition', async () => {
+ const browser = await webdriver(next.url, '/')
+ await browser.eval('window.renderAttempts = 0')
+
+ await browser.eval('window.next.router.push("/with-error")')
+ await check(() => browser.eval('location.pathname'), '/with-error')
+
+ // wait a bit to see if we are rendering multiple times unexpectedly
+ await waitFor(500)
+ expect(await browser.eval('window.renderAttempts')).toBeLessThan(10)
+
+ const html = await browser.eval('document.documentElement.innerHTML')
+ expect(html).not.toContain('from _app')
+ expect(html).toContain(
+ 'Application error: a client-side exception has occurred'
+ )
+ })
+})
diff --git a/test/production/required-server-files-i18n.test.ts b/test/production/required-server-files-i18n.test.ts
index a8316fc530da0ba..17c5df1c80dff2a 100644
--- a/test/production/required-server-files-i18n.test.ts
+++ b/test/production/required-server-files-i18n.test.ts
@@ -135,6 +135,27 @@ describe('should set-up next', () => {
if (server) await killApp(server)
})
+ it('should not apply locale redirect in minimal mode', async () => {
+ const res = await fetchViaHTTP(appPort, '/', undefined, {
+ redirect: 'manual',
+ headers: {
+ 'accept-language': 'fr',
+ },
+ })
+ expect(res.status).toBe(200)
+ expect(await res.text()).toContain('index page')
+
+ const resCookie = await fetchViaHTTP(appPort, '/', undefined, {
+ redirect: 'manual',
+ headers: {
+ 'accept-language': 'en',
+ cookie: 'NEXT_LOCALE=fr',
+ },
+ })
+ expect(resCookie.status).toBe(200)
+ expect(await resCookie.text()).toContain('index page')
+ })
+
it('should output required-server-files manifest correctly', async () => {
expect(requiredFilesManifest.version).toBe(1)
expect(Array.isArray(requiredFilesManifest.files)).toBe(true)
diff --git a/test/production/typescript-basic/app/pages/index.tsx b/test/production/typescript-basic/app/pages/index.tsx
index 85950f2e3452c56..e974da66bbafd91 100644
--- a/test/production/typescript-basic/app/pages/index.tsx
+++ b/test/production/typescript-basic/app/pages/index.tsx
@@ -1,6 +1,8 @@
import { useRouter } from 'next/router'
import Link from 'next/link'
import { type PageConfig } from 'next'
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+import { StyleRegistry, createStyleRegistry } from 'styled-jsx'
export const config: PageConfig = {}