Files
next.js/test/integration/i18n-support/test/shared.ts
Arian Tron 61f56f997c
Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled
first commit
2026-03-10 19:37:31 +03:30

3773 lines
123 KiB
TypeScript

/* eslint-env jest */
import glob from 'glob'
import fs from 'fs-extra'
import cheerio from 'cheerio'
import { join } from 'path'
import webdriver from 'next-webdriver'
import escapeRegex from 'escape-string-regexp'
import assert from 'assert'
import {
fetchViaHTTP,
renderViaHTTP,
waitFor,
normalizeRegEx,
check,
getDeploymentId,
} from 'next-test-utils'
const domainLocales = ['go', 'go-BE', 'do', 'do-BE']
export const nonDomainLocales = [
'en-US',
'nl-NL',
'nl-BE',
'nl',
'fr-BE',
'fr',
'en',
]
const defaultLocales = ['en-US', 'do', 'go']
export const locales = [...nonDomainLocales, ...domainLocales]
async function addDefaultLocaleCookie(browser) {
// make sure default locale is used in case browser isn't set to
// favor en-US by default, (we use all caps to ensure it's case-insensitive)
await browser.addCookie({ name: 'NEXT_LOCALE', value: 'EN-US' })
await browser.refresh()
}
export function runTests(ctx) {
if (ctx.basePath) {
it('should handle basePath like pathname', async () => {
const { basePath } = ctx
for (const pathname of [
`${basePath}extra`,
`/en${basePath}`,
`${basePath}extra/en`,
`${basePath}en`,
`/en${basePath}`,
]) {
const res = await fetchViaHTTP(ctx.appPort, pathname, undefined, {
redirect: 'manual',
})
expect(res.status).toBe(404)
expect(await res.text()).toContain('This page could not be found')
}
})
}
it('should 404 for locale prefixed static assets correctly', async () => {
const assets = glob.sync('**/*.js', {
cwd: join(ctx.appDir, '.next/static'),
})
const deploymentDpl = getDeploymentId(ctx.appDir, ctx.isDev)
// Only use a subset of the locales to speed up the test
for (const locale of [
...nonDomainLocales.slice(0, 2),
...domainLocales.slice(0, 2),
]) {
for (const asset of assets) {
const dpl = asset.includes(ctx.buildId)
? deploymentDpl.getDeploymentIdQuery()
: deploymentDpl.getAssetQuery()
// _next/static asset
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath || ''}/${locale}/_next/static/${encodeURI(asset)}${dpl}`,
undefined,
{ redirect: 'manual' }
)
if (!defaultLocales.includes(locale)) {
expect(res.status).toBe(404)
expect(res.headers.get('content-type')).toBe(
'text/plain; charset=utf-8'
)
expect(await res.text()).toBe('Not Found')
} else {
// We only 404 for non-default locale
expect(res.status).toBe(200)
}
}
}
})
it('should serve public file on locale domain', async () => {
for (const host of ['example.do', 'example.com']) {
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath || ''}/files/texts/file.txt`,
undefined,
{
headers: {
host,
},
}
)
expect(res.status).toBe(200)
expect(await res.text()).toContain('hello from file.txt')
}
})
it('should 404 for locale prefixed public folder files', async () => {
for (const locale of locales) {
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath || ''}/${locale}/files/texts/file.txt`,
undefined,
{ redirect: 'manual' }
)
if (!defaultLocales.includes(locale)) {
expect(res.status).toBe(404)
expect(await res.text()).toContain('could not be found')
} else {
// We only 404 for non-default locale
expect(res.status).toBe(200)
}
}
})
it('should redirect external domain correctly', async () => {
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath || ''}/do/redirect-5`,
undefined,
{
headers: {
'accept-language': 'do',
},
redirect: 'manual',
}
)
expect(res.status).toBe(307)
expect(res.headers.get('location')).toBe('https://jobs.example.com/')
const res2 = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath || ''}/redirect-5`,
undefined,
{
redirect: 'manual',
}
)
expect(res2.status).toBe(307)
expect(res2.headers.get('location')).toBe('https://jobs.example.com/')
const res3 = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath || ''}/fr/redirect-5`,
undefined,
{
redirect: 'manual',
}
)
expect(res3.status).toBe(307)
expect(res3.headers.get('location')).toBe('https://jobs.example.com/')
})
it('should have domainLocales available on useRouter', async () => {
const browser = await webdriver(ctx.appPort, `${ctx.basePath || '/'}`)
expect(
JSON.parse(await browser.elementByCss('#router-domain-locales').text())
).toEqual([
{
http: true,
domain: 'example.do',
defaultLocale: 'do',
locales: ['do-BE'],
},
{ domain: 'example.com', defaultLocale: 'go', locales: ['go-BE'] },
])
})
it('should not error with similar named cookie to locale cookie', async () => {
const res = await fetchViaHTTP(
ctx.appPort,
ctx.basePath || '/',
undefined,
{
headers: {
cookie: 'NEXT_LOCALE2=hello',
},
}
)
expect(res.status).toBe(200)
expect(await res.text()).toContain('index page')
})
it('should not add duplicate locale key when navigating back to root path with query params', async () => {
const basePath = ctx.basePath || ''
const queryKey = 'query'
const queryValue = '1'
const browser = await webdriver(
ctx.appPort,
`${basePath}/fr?${queryKey}=${queryValue}`
)
expect(await browser.eval(() => document.location.pathname)).toBe(
`${basePath}/fr`
)
expect(await browser.elementByCss('#router-pathname').text()).toBe('/')
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({ [queryKey]: queryValue })
expect(await browser.elementByCss('#router-locale').text()).toBe('fr')
await browser
.elementByCss('#to-another')
.click()
.waitForElementByCss('#another')
expect(await browser.eval(() => document.location.pathname)).toBe(
`${basePath}/fr/another`
)
expect(await browser.elementByCss('#router-pathname').text()).toBe(
'/another'
)
expect(await browser.elementByCss('#router-locale').text()).toBe('fr')
await browser.back().waitForElementByCss('#index')
expect(await browser.eval(() => document.location.pathname)).toBe(
`${basePath}/fr`
)
expect(await browser.elementByCss('#router-pathname').text()).toBe('/')
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({ [queryKey]: queryValue })
expect(await browser.elementByCss('#router-locale').text()).toBe('fr')
})
it('should not add duplicate locale key when navigating back to root path with hash', async () => {
const basePath = ctx.basePath || ''
const hashValue = '#anchor-1'
const browser = await webdriver(ctx.appPort, `${basePath}/fr${hashValue}`)
expect(await browser.eval(() => document.location.pathname)).toBe(
`${basePath}/fr`
)
expect(await browser.eval(() => document.location.hash)).toBe(hashValue)
expect(await browser.elementByCss('#router-pathname').text()).toBe('/')
expect(await browser.elementByCss('#router-locale').text()).toBe('fr')
await browser
.elementByCss('#to-another')
.click()
.waitForElementByCss('#another')
expect(await browser.eval(() => document.location.pathname)).toBe(
`${basePath}/fr/another`
)
expect(await browser.elementByCss('#router-pathname').text()).toBe(
'/another'
)
expect(await browser.elementByCss('#router-locale').text()).toBe('fr')
await browser.back().waitForElementByCss('#index')
expect(await browser.eval(() => document.location.pathname)).toBe(
`${basePath}/fr`
)
expect(await browser.eval(() => document.location.hash)).toBe(hashValue)
expect(await browser.elementByCss('#router-pathname').text()).toBe('/')
expect(await browser.elementByCss('#router-locale').text()).toBe('fr')
})
it('should handle navigating back to different casing of locale', async () => {
const browser = await webdriver(
ctx.appPort,
`${ctx.basePath || ''}/FR/links`
)
expect(await browser.eval(() => document.location.pathname)).toBe(
`${ctx.basePath || ''}/FR/links`
)
expect(await browser.elementByCss('#router-pathname').text()).toBe('/links')
expect(await browser.elementByCss('#router-locale').text()).toBe('fr')
await browser
.elementByCss('#to-another')
.click()
.waitForElementByCss('#another')
expect(await browser.eval(() => document.location.pathname)).toBe(
`${ctx.basePath || ''}/fr/another`
)
expect(await browser.elementByCss('#router-pathname').text()).toBe(
'/another'
)
expect(await browser.elementByCss('#router-locale').text()).toBe('fr')
await browser.back().waitForElementByCss('#links')
expect(await browser.eval(() => document.location.pathname)).toBe(
`${ctx.basePath || ''}/FR/links`
)
expect(await browser.elementByCss('#router-pathname').text()).toBe('/links')
expect(await browser.elementByCss('#router-locale').text()).toBe('fr')
})
it('should have correct initial query values for fallback', async () => {
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath || '/gsp/fallback/random-' + Date.now()}`
)
const html = await res.text()
const $ = cheerio.load(html)
expect(JSON.parse($('#router-query').text())).toEqual({})
})
it('should navigate to page with same name as development buildId', async () => {
const browser = await webdriver(ctx.appPort, `${ctx.basePath || '/'}`)
await browser.eval(`(function() {
window.beforeNav = 1
window.next.router.push('/developments')
})()`)
await browser.waitForElementByCss('#developments')
expect(await browser.eval('window.beforeNav')).toBe(1)
expect(await browser.elementByCss('#router-locale').text()).toBe('en-US')
expect(await browser.elementByCss('#router-default-locale').text()).toBe(
'en-US'
)
expect(await browser.elementByCss('#router-pathname').text()).toBe(
'/developments'
)
expect(await browser.elementByCss('#router-as-path').text()).toBe(
'/developments'
)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({})
expect(JSON.parse(await browser.elementByCss('#props').text())).toEqual({
locales,
locale: 'en-US',
defaultLocale: 'en-US',
})
})
// this test can not currently be tested in browser without modifying the
// host resolution since it needs a domain to test locale domains behavior
it.skip('should redirect to locale domain correctly client-side', async () => {
const browser = await webdriver(ctx.appPort, `${ctx.basePath || '/'}`)
await browser.eval(`(function() {
window.next.router.push(
window.next.router.pathname,
window.next.router.asPath,
{
locale: 'go'
}
)
})()`)
await check(() => browser.eval('window.location.hostname'), /example\.com/)
expect(await browser.eval('window.location.pathname')).toBe(
ctx.basePath || '/'
)
await browser.get(
// @ts-expect-error found when converting to TypeScript
browser.initUrl
)
await browser.waitForElementByCss('#index')
await browser.eval(`(function() {
window.next.router.push(
'/gssp',
undefined,
{
locale: 'go-BE'
}
)
})()`)
await check(() => browser.eval('window.location.hostname'), /example\.com/)
expect(await browser.eval('window.location.pathname')).toBe(
`${ctx.basePath || ''}/go-BE/gssp`
)
})
// this test can not currently be tested in browser without modifying the
// host resolution since it needs a domain to test locale domains behavior
it.skip('should render the correct href for locale domain', async () => {
let browser = await webdriver(
ctx.appPort,
`${ctx.basePath || ''}/links?nextLocale=go`
)
for (const [element, pathname] of [
['#to-another', '/another'],
['#to-gsp', '/gsp'],
['#to-fallback-first', '/gsp/fallback/first'],
['#to-fallback-hello', '/gsp/fallback/hello'],
['#to-gssp', '/gssp'],
['#to-gssp-slug', '/gssp/first'],
]) {
const href = await browser.elementByCss(element).getAttribute('href')
expect(href).toBe(`https://example.com${ctx.basePath || ''}${pathname}`)
}
expect(
await browser.elementByCss('#to-external').getAttribute('href')
).toBe('https://nextjs.org/')
browser = await webdriver(
ctx.appPort,
`${ctx.basePath || ''}/links?nextLocale=go-BE`
)
for (const [element, pathname] of [
['#to-another', '/another'],
['#to-gsp', '/gsp'],
['#to-fallback-first', '/gsp/fallback/first'],
['#to-fallback-hello', '/gsp/fallback/hello'],
['#to-gssp', '/gssp'],
['#to-gssp-slug', '/gssp/first'],
]) {
const href = await browser.elementByCss(element).getAttribute('href')
expect(href).toBe(
`https://example.com${ctx.basePath || ''}/go-BE${pathname}`
)
}
expect(
await browser.elementByCss('#to-external').getAttribute('href')
).toBe('https://nextjs.org/')
})
// The page is accessible on subpath as well as on the domain url without subpath.
// Once this is not the case the test will need to be changed to access it via domain.
it('should prerender with the correct href for locale domain', async () => {
let browser = await webdriver(ctx.appPort, `${ctx.basePath || ''}/go`)
for (const [element, pathname] of [
['#to-another', '/another'],
['#to-gsp', '/gsp'],
['#to-fallback-first', '/gsp/fallback/first'],
['#to-fallback-hello', '/gsp/fallback/hello'],
['#to-gssp', '/gssp'],
['#to-gssp-slug', '/gssp/first'],
]) {
const href = await browser.elementByCss(element).getAttribute('href')
expect(href).toBe(`https://example.com${ctx.basePath || ''}${pathname}`)
}
expect(
await browser.elementByCss('#to-external').getAttribute('href')
).toBe('https://nextjs.org/')
browser = await webdriver(ctx.appPort, `${ctx.basePath || ''}/go-BE`)
for (const [element, pathname] of [
['#to-another', '/another'],
['#to-gsp', '/gsp'],
['#to-fallback-first', '/gsp/fallback/first'],
['#to-fallback-hello', '/gsp/fallback/hello'],
['#to-gssp', '/gssp'],
['#to-gssp-slug', '/gssp/first'],
]) {
const href = await browser.elementByCss(element).getAttribute('href')
expect(href).toBe(
`https://example.com${ctx.basePath || ''}/go-BE${pathname}`
)
}
expect(
await browser.elementByCss('#to-external').getAttribute('href')
).toBe('https://nextjs.org/')
})
it('should render the correct href with locale domains but not on a locale domain', async () => {
let browser = await webdriver(
ctx.appPort,
`${ctx.basePath || ''}/links?nextLocale=go`
)
let baseURL = await browser.url()
for (const [element, pathname] of [
['#to-another', '/another'],
['#to-gsp', '/gsp'],
['#to-fallback-first', '/gsp/fallback/first'],
['#to-fallback-hello', '/gsp/fallback/hello'],
['#to-gssp', '/gssp'],
['#to-gssp-slug', '/gssp/first'],
]) {
const href = await browser.elementByCss(element).getAttribute('href')
const { hostname, pathname: hrefPathname } = new URL(href, baseURL)
expect(hostname).not.toBe('example.com')
expect(hrefPathname).toBe(`${ctx.basePath || ''}/go${pathname}`)
}
browser = await webdriver(
ctx.appPort,
`${ctx.basePath || ''}/links?nextLocale=go-BE`
)
baseURL = await browser.url()
for (const [element, pathname] of [
['#to-another', '/another'],
['#to-gsp', '/gsp'],
['#to-fallback-first', '/gsp/fallback/first'],
['#to-fallback-hello', '/gsp/fallback/hello'],
['#to-gssp', '/gssp'],
['#to-gssp-slug', '/gssp/first'],
]) {
const href = await browser.elementByCss(element).getAttribute('href')
const { hostname, pathname: hrefPathname } = new URL(href, baseURL)
expect(hostname).not.toBe('example.com')
expect(hrefPathname).toBe(`${ctx.basePath || ''}/go-BE${pathname}`)
}
})
it('should navigate through history with query correctly', async () => {
const browser = await webdriver(ctx.appPort, `${ctx.basePath || '/'}`)
await browser.eval(`(function() {
window.beforeNav = 1
window.next.router.push(
window.next.router.pathname,
window.next.router.asPath,
{ locale: 'nl' }
)
})()`)
await check(() => browser.elementByCss('#router-locale').text(), 'nl')
expect(await browser.eval('window.beforeNav')).toBe(1)
await browser.eval(`(function() {
window.next.router.push(
'/gssp?page=1'
)
})()`)
await check(async () => {
const html = await browser.eval('document.documentElement.innerHTML')
const props = JSON.parse(cheerio.load(html)('#props').text())
assert.deepEqual(props, {
locale: 'nl',
locales,
defaultLocale: 'en-US',
query: { page: '1' },
})
return 'success'
}, 'success')
await browser
.back()
.waitForElementByCss('#index')
.forward()
.waitForElementByCss('#gssp')
const props2 = JSON.parse(await browser.elementByCss('#props').text())
expect(props2).toEqual({
locale: 'nl',
locales,
defaultLocale: 'en-US',
query: { page: '1' },
})
expect(await browser.eval('window.beforeNav')).toBe(1)
})
if (!ctx.isDev) {
it('should not contain backslashes in pages-manifest', async () => {
const pagesManifestContent = await fs.readFile(
join(ctx.buildPagesDir, '../pages-manifest.json'),
'utf8'
)
expect(pagesManifestContent).not.toContain('\\')
expect(pagesManifestContent).toContain('/')
})
}
it('should resolve href correctly when dynamic route matches locale prefixed', async () => {
const browser = await webdriver(ctx.appPort, `${ctx.basePath}/nl`)
await browser.eval('window.beforeNav = 1')
await browser.eval(`(function() {
window.next.router.push('/post-1?a=b')
})()`)
await browser.waitForElementByCss('#post')
const router = JSON.parse(await browser.elementByCss('#router').text())
expect(router.query).toEqual({ post: 'post-1', a: 'b' })
expect(router.pathname).toBe('/[post]')
expect(router.asPath).toBe('/post-1?a=b')
expect(router.locale).toBe('nl')
await browser.back().waitForElementByCss('#index')
expect(await browser.elementByCss('#router-locale').text()).toBe('nl')
expect(await browser.eval('window.beforeNav')).toBe(1)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({})
})
it('should use default locale when no locale is in href with locale false', async () => {
const browser = await webdriver(
ctx.appPort,
`${ctx.basePath}/nl/locale-false?nextLocale=fr`
)
await browser.eval('window.beforeNav = 1')
if (!ctx.isDev) {
await browser.eval(`(function() {
document.querySelector('#to-gssp-slug-default').scrollIntoView()
document.querySelector('#to-gsp-default').scrollIntoView()
})()`)
await check(async () => {
const hrefs = await browser.eval(`Object.keys(window.next.router.sdc)`)
hrefs.sort()
assert.deepEqual(
hrefs.map((href) =>
new URL(href).pathname
.replace(ctx.basePath, '')
.replace(/^\/_next\/data\/[^/]+/, '')
),
[
'/en-US/gsp.json',
'/fr/gsp.json',
'/fr/gsp/fallback/first.json',
'/fr/gsp/fallback/hello.json',
]
)
return 'yes'
}, 'yes')
}
expect(await browser.eval('window.beforeNav')).toBe(1)
})
if (ctx.isDev) {
it('should show error for redirect and notFound returned at same time', async () => {
const html = await renderViaHTTP(
ctx.appPort,
`${ctx.basePath}/_next/data/development/en/gsp/fallback/mixed-not-found-redirect.json`
)
expect(html).toContain(
'`redirect` and `notFound` can not both be returned from getStaticProps at the same time. Page: /gsp/fallback/[slug]'
)
})
} else {
it('should preload all locales data correctly', async () => {
const browser = await webdriver(ctx.appPort, `${ctx.basePath}/mixed`)
await browser.eval(`(function() {
document.querySelector('#to-gsp-en-us').scrollIntoView()
document.querySelector('#to-gsp-nl-nl').scrollIntoView()
document.querySelector('#to-gsp-fr').scrollIntoView()
})()`)
await check(async () => {
const hrefs = await browser.eval(`Object.keys(window.next.router.sdc)`)
hrefs.sort()
assert.deepEqual(
hrefs.map((href) =>
new URL(href).pathname
.replace(ctx.basePath, '')
.replace(/^\/_next\/data\/[^/]+/, '')
),
['/en-US/gsp.json', '/fr.json', '/fr/gsp.json', '/nl-NL/gsp.json']
)
return 'yes'
}, 'yes')
})
}
it('should have correct values for non-prefixed path', async () => {
for (const paths of [
['/links', '/links'],
['/another', '/another'],
['/gsp/fallback/first', '/gsp/fallback/[slug]'],
['/gsp/no-fallback/first', '/gsp/no-fallback/[slug]'],
]) {
const [asPath, pathname] = paths
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath}${asPath}`,
undefined,
{
redirect: 'manual',
headers: {
'accept-language': 'fr',
},
}
)
expect(res.status).toBe(200)
const $ = cheerio.load(await res.text())
expect($('html').attr('lang')).toBe('en-US')
expect($('#router-locale').text()).toBe('en-US')
expect($('#router-default-locale').text()).toBe('en-US')
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect($('#router-pathname').text()).toBe(pathname)
expect($('#router-as-path').text()).toBe(asPath)
}
})
it('should not have hydration mis-match from hash', async () => {
const browser = await webdriver(ctx.appPort, `${ctx.basePath}/en#`)
expect(await browser.elementByCss('html').getAttribute('lang')).toBe('en')
expect(await browser.elementByCss('#router-locale').text()).toBe('en')
expect(await browser.elementByCss('#router-default-locale').text()).toBe(
'en-US'
)
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(await browser.elementByCss('#router-pathname').text()).toBe('/')
expect(await browser.elementByCss('#router-as-path').text()).toBe('/')
expect(await browser.eval('window.caughtWarns')).toEqual([])
})
if (!ctx.isDev) {
it('should add i18n config to routes-manifest', async () => {
const routesManifest = await fs.readJSON(
join(ctx.appDir, '.next/routes-manifest.json')
)
expect(routesManifest.i18n).toEqual({
locales,
defaultLocale: 'en-US',
domains: [
{
http: true,
domain: 'example.do',
defaultLocale: 'do',
locales: ['do-BE'],
},
{
domain: 'example.com',
defaultLocale: 'go',
locales: ['go-BE'],
},
],
})
})
it('should output correct prerender-manifest', async () => {
const prerenderManifest = await fs.readJSON(
join(ctx.appDir, '.next/prerender-manifest.json')
)
const staticRoutes = {}
const dynamicRoutes = {}
for (const key of Object.keys(prerenderManifest.routes).sort()) {
const item = prerenderManifest.routes[key]
staticRoutes[key] = item
}
for (const key of Object.keys(prerenderManifest.dynamicRoutes).sort()) {
const item = prerenderManifest.dynamicRoutes[key]
item.routeRegex = normalizeRegEx(item.routeRegex)
item.dataRouteRegex = normalizeRegEx(item.dataRouteRegex)
dynamicRoutes[key] = item
}
expect(
JSON.stringify(staticRoutes, null, 2)
.replace(/\\\\/g, '\\')
.replace(new RegExp(escapeRegex(ctx.buildId), 'g'), 'BUILD_ID')
).toMatchInlineSnapshot(`
"{
"/do": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/do.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/do-BE": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/do-BE.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/do-BE/404": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/do-BE/404.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/do-BE/frank": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/do-BE/frank.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/do-BE/gsp": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/do-BE/gsp.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/do-BE/gsp/fallback/always": {
"initialRevalidateSeconds": false,
"srcRoute": "/gsp/fallback/[slug]",
"dataRoute": "/_next/data/BUILD_ID/do-BE/gsp/fallback/always.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/do-BE/not-found": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/do-BE/not-found.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/do/404": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/do/404.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/do/frank": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/do/frank.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/do/gsp": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/do/gsp.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/do/gsp/fallback/always": {
"initialRevalidateSeconds": false,
"srcRoute": "/gsp/fallback/[slug]",
"dataRoute": "/_next/data/BUILD_ID/do/gsp/fallback/always.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/do/not-found": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/do/not-found.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/en": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/en.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/en-US": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/en-US.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/en-US/404": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/en-US/404.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/en-US/frank": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/en-US/frank.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/en-US/gsp": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/en-US/gsp.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/en-US/gsp/fallback/always": {
"initialRevalidateSeconds": false,
"srcRoute": "/gsp/fallback/[slug]",
"dataRoute": "/_next/data/BUILD_ID/en-US/gsp/fallback/always.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/en-US/gsp/fallback/first": {
"initialRevalidateSeconds": false,
"srcRoute": "/gsp/fallback/[slug]",
"dataRoute": "/_next/data/BUILD_ID/en-US/gsp/fallback/first.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/en-US/gsp/fallback/second": {
"initialRevalidateSeconds": false,
"srcRoute": "/gsp/fallback/[slug]",
"dataRoute": "/_next/data/BUILD_ID/en-US/gsp/fallback/second.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/en-US/gsp/no-fallback/first": {
"initialRevalidateSeconds": false,
"srcRoute": "/gsp/no-fallback/[slug]",
"dataRoute": "/_next/data/BUILD_ID/en-US/gsp/no-fallback/first.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/en-US/gsp/no-fallback/second": {
"initialRevalidateSeconds": false,
"srcRoute": "/gsp/no-fallback/[slug]",
"dataRoute": "/_next/data/BUILD_ID/en-US/gsp/no-fallback/second.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/en-US/not-found": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/en-US/not-found.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/en-US/not-found/blocking-fallback/first": {
"initialRevalidateSeconds": false,
"srcRoute": "/not-found/blocking-fallback/[slug]",
"dataRoute": "/_next/data/BUILD_ID/en-US/not-found/blocking-fallback/first.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/en-US/not-found/blocking-fallback/second": {
"initialRevalidateSeconds": false,
"srcRoute": "/not-found/blocking-fallback/[slug]",
"dataRoute": "/_next/data/BUILD_ID/en-US/not-found/blocking-fallback/second.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/en-US/not-found/fallback/first": {
"initialRevalidateSeconds": false,
"srcRoute": "/not-found/fallback/[slug]",
"dataRoute": "/_next/data/BUILD_ID/en-US/not-found/fallback/first.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/en-US/not-found/fallback/second": {
"initialRevalidateSeconds": false,
"srcRoute": "/not-found/fallback/[slug]",
"dataRoute": "/_next/data/BUILD_ID/en-US/not-found/fallback/second.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/en/404": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/en/404.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/en/frank": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/en/frank.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/en/gsp": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/en/gsp.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/en/gsp/fallback/always": {
"initialRevalidateSeconds": false,
"srcRoute": "/gsp/fallback/[slug]",
"dataRoute": "/_next/data/BUILD_ID/en/gsp/fallback/always.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/en/not-found": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/en/not-found.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/fr": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/fr.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/fr-BE": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/fr-BE.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/fr-BE/404": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/fr-BE/404.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/fr-BE/frank": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/fr-BE/frank.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/fr-BE/gsp": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/fr-BE/gsp.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/fr-BE/gsp/fallback/always": {
"initialRevalidateSeconds": false,
"srcRoute": "/gsp/fallback/[slug]",
"dataRoute": "/_next/data/BUILD_ID/fr-BE/gsp/fallback/always.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/fr-BE/not-found": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/fr-BE/not-found.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/fr/404": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/fr/404.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/fr/frank": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/fr/frank.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/fr/gsp": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/fr/gsp.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/fr/gsp/fallback/always": {
"initialRevalidateSeconds": false,
"srcRoute": "/gsp/fallback/[slug]",
"dataRoute": "/_next/data/BUILD_ID/fr/gsp/fallback/always.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/fr/not-found": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/fr/not-found.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/go": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/go.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/go-BE": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/go-BE.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/go-BE/404": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/go-BE/404.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/go-BE/frank": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/go-BE/frank.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/go-BE/gsp": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/go-BE/gsp.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/go-BE/gsp/fallback/always": {
"initialRevalidateSeconds": false,
"srcRoute": "/gsp/fallback/[slug]",
"dataRoute": "/_next/data/BUILD_ID/go-BE/gsp/fallback/always.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/go-BE/not-found": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/go-BE/not-found.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/go/404": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/go/404.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/go/frank": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/go/frank.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/go/gsp": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/go/gsp.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/go/gsp/fallback/always": {
"initialRevalidateSeconds": false,
"srcRoute": "/gsp/fallback/[slug]",
"dataRoute": "/_next/data/BUILD_ID/go/gsp/fallback/always.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/go/not-found": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/go/not-found.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/nl": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/nl.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/nl-BE": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/nl-BE.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/nl-BE/404": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/nl-BE/404.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/nl-BE/frank": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/nl-BE/frank.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/nl-BE/gsp": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/nl-BE/gsp.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/nl-BE/gsp/fallback/always": {
"initialRevalidateSeconds": false,
"srcRoute": "/gsp/fallback/[slug]",
"dataRoute": "/_next/data/BUILD_ID/nl-BE/gsp/fallback/always.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/nl-BE/not-found": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/nl-BE/not-found.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/nl-NL": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/nl-NL.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/nl-NL/404": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/nl-NL/404.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/nl-NL/frank": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/nl-NL/frank.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/nl-NL/gsp": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/nl-NL/gsp.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/nl-NL/gsp/fallback/always": {
"initialRevalidateSeconds": false,
"srcRoute": "/gsp/fallback/[slug]",
"dataRoute": "/_next/data/BUILD_ID/nl-NL/gsp/fallback/always.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/nl-NL/gsp/no-fallback/second": {
"initialRevalidateSeconds": false,
"srcRoute": "/gsp/no-fallback/[slug]",
"dataRoute": "/_next/data/BUILD_ID/nl-NL/gsp/no-fallback/second.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/nl-NL/not-found": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/nl-NL/not-found.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/nl/404": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/nl/404.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/nl/frank": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/nl/frank.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/nl/gsp": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/nl/gsp.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/nl/gsp/fallback/always": {
"initialRevalidateSeconds": false,
"srcRoute": "/gsp/fallback/[slug]",
"dataRoute": "/_next/data/BUILD_ID/nl/gsp/fallback/always.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/nl/not-found": {
"initialRevalidateSeconds": false,
"srcRoute": null,
"dataRoute": "/_next/data/BUILD_ID/nl/not-found.json",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
}
}"
`)
expect(
JSON.stringify(dynamicRoutes, null, 2)
.replace(/\\\\/g, '\\')
.replace(new RegExp(escapeRegex(ctx.buildId), 'g'), 'BUILD_ID')
.replace(
new RegExp(escapeRegex(escapeRegex(ctx.buildId)), 'g'),
'BUILD_ID'
)
).toMatchInlineSnapshot(`
"{
"/gsp/fallback/[slug]": {
"routeRegex": "^\\/gsp\\/fallback\\/([^\\/]+?)(?:\\/)?$",
"dataRoute": "/_next/data/BUILD_ID/gsp/fallback/[slug].json",
"fallback": "/gsp/fallback/[slug].html",
"dataRouteRegex": "^\\/_next\\/data\\/BUILD_ID\\/gsp\\/fallback\\/([^\\/]+?)\\.json$",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/gsp/no-fallback/[slug]": {
"routeRegex": "^\\/gsp\\/no\\-fallback\\/([^\\/]+?)(?:\\/)?$",
"dataRoute": "/_next/data/BUILD_ID/gsp/no-fallback/[slug].json",
"fallback": false,
"dataRouteRegex": "^\\/_next\\/data\\/BUILD_ID\\/gsp\\/no\\-fallback\\/([^\\/]+?)\\.json$",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/not-found/blocking-fallback/[slug]": {
"routeRegex": "^\\/not\\-found\\/blocking\\-fallback\\/([^\\/]+?)(?:\\/)?$",
"dataRoute": "/_next/data/BUILD_ID/not-found/blocking-fallback/[slug].json",
"fallback": null,
"dataRouteRegex": "^\\/_next\\/data\\/BUILD_ID\\/not\\-found\\/blocking\\-fallback\\/([^\\/]+?)\\.json$",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
},
"/not-found/fallback/[slug]": {
"routeRegex": "^\\/not\\-found\\/fallback\\/([^\\/]+?)(?:\\/)?$",
"dataRoute": "/_next/data/BUILD_ID/not-found/fallback/[slug].json",
"fallback": "/not-found/fallback/[slug].html",
"dataRouteRegex": "^\\/_next\\/data\\/BUILD_ID\\/not\\-found\\/fallback\\/([^\\/]+?)\\.json$",
"allowHeader": [
"host",
"x-matched-path",
"x-prerender-revalidate",
"x-prerender-revalidate-if-generated",
"x-next-revalidated-tags",
"x-next-revalidate-tag-token"
]
}
}"
`)
})
}
it('should resolve auto-export dynamic route correctly', async () => {
for (const locale of nonDomainLocales) {
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath}/${locale}/dynamic/first`,
undefined,
{
redirect: 'manual',
}
)
expect(res.status).toBe(200)
expect(await res.text()).toContain('dynamic page')
}
})
it('should navigate to auto-export dynamic page', async () => {
for (const locale of nonDomainLocales) {
const browser = await webdriver(ctx.appPort, `${ctx.basePath}/${locale}`)
await browser.eval('window.beforeNav = 1')
await browser
.elementByCss('#to-dynamic')
.click()
.waitForElementByCss('#dynamic')
expect(await browser.eval('window.beforeNav')).toBe(1)
expect(await browser.elementByCss('#router-locale').text()).toBe(locale)
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({ slug: 'first' })
expect(await browser.elementByCss('#router-pathname').text()).toBe(
'/dynamic/[slug]'
)
expect(await browser.elementByCss('#router-as-path').text()).toBe(
'/dynamic/first'
)
expect(await browser.eval('window.location.pathname')).toBe(
`${
locale === 'en-US' ? `${ctx.basePath}` : `${ctx.basePath}/${locale}`
}/dynamic/first`
)
await browser.back().waitForElementByCss('#index')
expect(await browser.eval('window.beforeNav')).toBe(1)
expect(await browser.elementByCss('#router-locale').text()).toBe(locale)
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({})
expect(await browser.elementByCss('#router-pathname').text()).toBe('/')
expect(await browser.elementByCss('#router-as-path').text()).toBe('/')
}
})
it('should apply trailingSlash redirect correctly', async () => {
for (const [testPath, path, hostname, query] of [
['/first/', '/first', 'localhost', {}],
['/en/', '/en', 'localhost', {}],
['/en/another/', '/en/another', 'localhost', {}],
['/fr/', '/fr', 'localhost', {}],
['/fr/another/', '/fr/another', 'localhost', {}],
] as const) {
const res = await fetchViaHTTP(ctx.appPort, testPath, undefined, {
redirect: 'manual',
})
expect(res.status).toBe(308)
const parsed = new URL(res.headers.get('location'))
expect(parsed.pathname).toBe(path)
if (hostname === 'localhost') {
expect(parsed.hostname).toBeOneOf([hostname, '127.0.0.1'])
} else {
expect(parsed.hostname).toBe(hostname)
}
expect(Object.fromEntries(parsed.searchParams.entries())).toEqual(query)
}
})
it('should apply redirects correctly', async () => {
for (const [path, shouldRedirect, locale, pathname] of [
['/en-US/redirect-1', true],
['/en/redirect-1', false],
['/nl/redirect-2', true],
['/fr/redirect-2', false],
['/redirect-3', true],
['/en/redirect-3', true, '/en'],
['/nl-NL/redirect-3', true, '/nl-NL'],
['/redirect-4', true, null, '/'],
['/nl/redirect-4', true, null, '/nl'],
]) {
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath}${path}`,
undefined,
{
redirect: 'manual',
}
)
expect(res.status).toBe(shouldRedirect ? 307 : 200)
if (shouldRedirect) {
const parsed = new URL(res.headers.get('location'))
expect(parsed.pathname).toBe(
`${ctx.basePath}${locale || ''}${pathname || '/somewhere-else'}`
)
expect(Object.fromEntries(parsed.searchParams.entries())).toEqual({})
}
}
})
it('should apply headers correctly', async () => {
for (const [path, shouldAdd] of [
['/en-US/add-header-1', true],
['/en/add-header-1', false],
['/nl/add-header-2', true],
['/fr/add-header-2', false],
['/add-header-3', true],
['/en/add-header-3', true],
['/nl-NL/add-header-3', true],
] as const) {
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath}${path}`,
undefined,
{
redirect: 'manual',
}
)
expect(res.status).toBe(200)
expect(res.headers.get('x-hello')).toBe(shouldAdd ? 'world' : null)
}
})
it('should visit API route directly correctly', async () => {
for (const locale of locales) {
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath || ''}/${locale}/api/hello`,
undefined,
{
redirect: 'manual',
}
)
expect(res.status).toBe(404)
}
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath || ''}/api/hello`
)
const data = await res.json()
expect(data).toEqual({
hello: true,
query: {},
})
})
it('should visit dynamic API route directly correctly', async () => {
for (const locale of locales) {
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath || ''}/${locale}/api/post/first`,
undefined,
{
redirect: 'manual',
}
)
expect(res.status).toBe(404)
}
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath || ''}/api/post/first`
)
const data = await res.json()
expect(data).toEqual({
post: true,
query: {
slug: 'first',
},
})
})
it.each(locales)(
'should rewrite to API route correctly for %s locale',
async (locale) => {
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath || ''}${
locale === 'en-US' ? '' : `/${locale}`
}/sitemap.xml`,
undefined,
{
redirect: 'manual',
}
)
expect(res.headers.get('content-type')).toContain('application/json')
const data = await res.json()
expect(data).toEqual({
hello: true,
query: {},
})
}
)
it('should apply rewrites correctly', async () => {
let res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath}/en-US/rewrite-1`,
undefined,
{
redirect: 'manual',
}
)
expect(res.status).toBe(200)
let html = await res.text()
let $ = cheerio.load(html)
expect($('html').attr('lang')).toBe('en-US')
expect($('#router-locale').text()).toBe('en-US')
expect($('#router-pathname').text()).toBe('/another')
expect($('#router-as-path').text()).toBe('/rewrite-1')
res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath}/nl/rewrite-2`,
undefined,
{
redirect: 'manual',
}
)
expect(res.status).toBe(200)
html = await res.text()
$ = cheerio.load(html)
expect($('html').attr('lang')).toBe('nl')
expect($('#router-locale').text()).toBe('nl')
expect($('#router-pathname').text()).toBe('/another')
expect($('#router-as-path').text()).toBe('/rewrite-2')
res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath}/fr/rewrite-3`,
undefined,
{
redirect: 'manual',
}
)
expect(res.status).toBe(200)
html = await res.text()
$ = cheerio.load(html)
expect($('html').attr('lang')).toBe('nl')
expect($('#router-locale').text()).toBe('nl')
expect($('#router-pathname').text()).toBe('/another')
expect($('#router-as-path').text()).toBe('/rewrite-3')
for (const locale of nonDomainLocales) {
res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath}/${locale}/rewrite-4`,
undefined,
{
redirect: 'manual',
}
)
expect(res.status).toBe(200)
html = await res.text()
$ = cheerio.load(html)
expect($('html').attr('lang')).toBe(locale)
expect($('#router-locale').text()).toBe(locale)
expect($('#router-pathname').text()).toBe('/another')
expect($('#router-as-path').text()).toBe('/rewrite-4')
res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath}/${locale}/rewrite-5`,
undefined,
{
redirect: 'manual',
}
)
expect(res.status).toBe(200)
const json = await res.json()
expect(json.url).toBe('/')
expect(json.external).toBe(true)
}
})
it('should navigate with locale prop correctly', async () => {
const browser = await webdriver(
ctx.appPort,
`${ctx.basePath}/links?nextLocale=fr`
)
await addDefaultLocaleCookie(browser)
await browser.eval('window.beforeNav = 1')
if (!ctx.isDev) {
await browser.eval(`(function() {
document.querySelector('#to-gsp').scrollIntoView()
document.querySelector('#to-fallback-first').scrollIntoView()
document.querySelector('#to-no-fallback-first').scrollIntoView()
})()`)
await check(async () => {
const hrefs = await browser.eval(`Object.keys(window.next.router.sdc)`)
hrefs.sort()
assert.deepEqual(
hrefs.map((href) =>
new URL(href).pathname
.replace(ctx.basePath, '')
.replace(/^\/_next\/data\/[^/]+/, '')
),
[
'/fr/gsp.json',
'/fr/gsp/fallback/first.json',
'/fr/gsp/fallback/hello.json',
]
)
return 'yes'
}, 'yes')
}
expect(await browser.elementByCss('#router-pathname').text()).toBe('/links')
expect(await browser.elementByCss('#router-as-path').text()).toBe(
'/links?nextLocale=fr'
)
expect(await browser.elementByCss('#router-locale').text()).toBe('en-US')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({ nextLocale: 'fr' })
expect(await browser.elementByCss('html').getAttribute('lang')).toBe(
'en-US'
)
await browser.elementByCss('#to-another').click()
await browser.waitForElementByCss('#another')
expect(await browser.elementByCss('#router-pathname').text()).toBe(
'/another'
)
expect(await browser.elementByCss('#router-as-path').text()).toBe(
'/another'
)
expect(await browser.elementByCss('#router-locale').text()).toBe('fr')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({})
expect(await browser.elementByCss('html').getAttribute('lang')).toBe('fr')
let parsedUrl = new URL(await browser.eval('window.location.href'))
expect(parsedUrl.pathname).toBe(`${ctx.basePath}/fr/another`)
expect(Object.fromEntries(parsedUrl.searchParams.entries())).toEqual({})
await browser.eval('window.history.back()')
await browser.waitForElementByCss('#links')
expect(await browser.elementByCss('#router-pathname').text()).toBe('/links')
expect(await browser.elementByCss('#router-as-path').text()).toBe(
'/links?nextLocale=fr'
)
expect(await browser.elementByCss('#router-locale').text()).toBe('en-US')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({ nextLocale: 'fr' })
expect(await browser.elementByCss('html').getAttribute('lang')).toBe(
'en-US'
)
parsedUrl = new URL(await browser.eval('window.location.href'))
expect(parsedUrl.pathname).toBe(`${ctx.basePath}/links`)
expect(Object.fromEntries(parsedUrl.searchParams.entries())).toEqual({
nextLocale: 'fr',
})
await browser.eval('window.history.forward()')
await browser.waitForElementByCss('#another')
expect(await browser.elementByCss('#router-pathname').text()).toBe(
'/another'
)
expect(await browser.elementByCss('#router-as-path').text()).toBe(
'/another'
)
expect(await browser.elementByCss('#router-locale').text()).toBe('fr')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({})
expect(await browser.elementByCss('html').getAttribute('lang')).toBe('fr')
parsedUrl = new URL(await browser.eval('window.location.href'))
expect(parsedUrl.pathname).toBe(`${ctx.basePath}/fr/another`)
expect(Object.fromEntries(parsedUrl.searchParams.entries())).toEqual({})
expect(await browser.eval('window.beforeNav')).toBe(1)
expect(await browser.eval('window.caughtWarns')).toEqual([])
})
it('should navigate with locale prop correctly GSP', async () => {
const browser = await webdriver(
ctx.appPort,
`${ctx.basePath}/links?nextLocale=nl`
)
await addDefaultLocaleCookie(browser)
await browser.eval('window.beforeNav = 1')
expect(await browser.elementByCss('#router-pathname').text()).toBe('/links')
expect(await browser.elementByCss('#router-as-path').text()).toBe(
'/links?nextLocale=nl'
)
expect(await browser.elementByCss('#router-locale').text()).toBe('en-US')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({ nextLocale: 'nl' })
expect(await browser.elementByCss('html').getAttribute('lang')).toBe(
'en-US'
)
await browser.elementByCss('#to-fallback-first').click()
await browser.waitForElementByCss('#gsp')
expect(await browser.elementByCss('#router-pathname').text()).toBe(
'/gsp/fallback/[slug]'
)
expect(await browser.elementByCss('#router-as-path').text()).toBe(
'/gsp/fallback/first'
)
expect(await browser.elementByCss('#router-locale').text()).toBe('nl')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({ slug: 'first' })
expect(await browser.elementByCss('html').getAttribute('lang')).toBe('nl')
let parsedUrl = new URL(await browser.eval('window.location.href'))
expect(parsedUrl.pathname).toBe(`${ctx.basePath}/nl/gsp/fallback/first`)
expect(Object.fromEntries(parsedUrl.searchParams.entries())).toEqual({})
await browser.eval('window.history.back()')
await browser.waitForElementByCss('#links')
expect(await browser.elementByCss('#router-pathname').text()).toBe('/links')
expect(await browser.elementByCss('#router-as-path').text()).toBe(
'/links?nextLocale=nl'
)
expect(await browser.elementByCss('#router-locale').text()).toBe('en-US')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({ nextLocale: 'nl' })
expect(await browser.elementByCss('html').getAttribute('lang')).toBe(
'en-US'
)
parsedUrl = new URL(await browser.eval('window.location.href'))
expect(parsedUrl.pathname).toBe(`${ctx.basePath}/links`)
expect(Object.fromEntries(parsedUrl.searchParams.entries())).toEqual({
nextLocale: 'nl',
})
await browser.eval('window.history.forward()')
await browser.waitForElementByCss('#gsp')
expect(await browser.elementByCss('#router-pathname').text()).toBe(
'/gsp/fallback/[slug]'
)
expect(await browser.elementByCss('#router-as-path').text()).toBe(
'/gsp/fallback/first'
)
expect(await browser.elementByCss('#router-locale').text()).toBe('nl')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({ slug: 'first' })
expect(await browser.elementByCss('html').getAttribute('lang')).toBe('nl')
parsedUrl = new URL(await browser.eval('window.location.href'))
expect(parsedUrl.pathname).toBe(`${ctx.basePath}/nl/gsp/fallback/first`)
expect(Object.fromEntries(parsedUrl.searchParams.entries())).toEqual({})
expect(await browser.eval('window.beforeNav')).toBe(1)
expect(await browser.eval('window.caughtWarns')).toEqual([])
})
it('should navigate with locale false correctly', async () => {
const browser = await webdriver(
ctx.appPort,
`${ctx.basePath}/locale-false?nextLocale=fr`
)
await addDefaultLocaleCookie(browser)
await browser.eval('window.beforeNav = 1')
if (!ctx.isDev) {
await browser.eval(`(function() {
document.querySelector('#to-gsp').scrollIntoView()
document.querySelector('#to-fallback-first').scrollIntoView()
document.querySelector('#to-no-fallback-first').scrollIntoView()
})()`)
await check(async () => {
const hrefs = await browser.eval(`Object.keys(window.next.router.sdc)`)
hrefs.sort()
assert.deepEqual(
hrefs.map((href) =>
new URL(href).pathname
.replace(ctx.basePath, '')
.replace(/^\/_next\/data\/[^/]+/, '')
),
[
'/en-US/gsp.json',
'/fr/gsp.json',
'/fr/gsp/fallback/first.json',
'/fr/gsp/fallback/hello.json',
]
)
return 'yes'
}, 'yes')
}
expect(await browser.elementByCss('#router-pathname').text()).toBe(
'/locale-false'
)
expect(await browser.elementByCss('#router-as-path').text()).toBe(
'/locale-false?nextLocale=fr'
)
expect(await browser.elementByCss('#router-locale').text()).toBe('en-US')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({ nextLocale: 'fr' })
expect(await browser.elementByCss('html').getAttribute('lang')).toBe(
'en-US'
)
await browser.elementByCss('#to-another').click()
await browser.waitForElementByCss('#another')
expect(await browser.elementByCss('#router-pathname').text()).toBe(
'/another'
)
expect(await browser.elementByCss('#router-as-path').text()).toBe(
'/another'
)
expect(await browser.elementByCss('#router-locale').text()).toBe('fr')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({})
expect(await browser.elementByCss('html').getAttribute('lang')).toBe('fr')
let parsedUrl = new URL(await browser.eval('window.location.href'))
expect(parsedUrl.pathname).toBe(`${ctx.basePath}/fr/another`)
expect(Object.fromEntries(parsedUrl.searchParams.entries())).toEqual({})
await browser.eval('window.history.back()')
await browser.waitForElementByCss('#links')
expect(await browser.elementByCss('#router-pathname').text()).toBe(
'/locale-false'
)
expect(await browser.elementByCss('#router-as-path').text()).toBe(
'/locale-false?nextLocale=fr'
)
expect(await browser.elementByCss('#router-locale').text()).toBe('en-US')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({ nextLocale: 'fr' })
expect(await browser.elementByCss('html').getAttribute('lang')).toBe(
'en-US'
)
parsedUrl = new URL(await browser.eval('window.location.href'))
expect(parsedUrl.pathname).toBe(`${ctx.basePath}/locale-false`)
expect(Object.fromEntries(parsedUrl.searchParams.entries())).toEqual({
nextLocale: 'fr',
})
await browser.eval('window.history.forward()')
await browser.waitForElementByCss('#another')
expect(await browser.elementByCss('#router-pathname').text()).toBe(
'/another'
)
expect(await browser.elementByCss('#router-as-path').text()).toBe(
'/another'
)
expect(await browser.elementByCss('#router-locale').text()).toBe('fr')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({})
expect(await browser.elementByCss('html').getAttribute('lang')).toBe('fr')
parsedUrl = new URL(await browser.eval('window.location.href'))
expect(parsedUrl.pathname).toBe(`${ctx.basePath}/fr/another`)
expect(Object.fromEntries(parsedUrl.searchParams.entries())).toEqual({})
expect(await browser.eval('window.beforeNav')).toBe(1)
expect(await browser.eval('window.caughtWarns')).toEqual([])
})
it('should navigate with locale false correctly GSP', async () => {
const browser = await webdriver(
ctx.appPort,
`${ctx.basePath}/locale-false?nextLocale=nl`
)
await addDefaultLocaleCookie(browser)
await browser.eval('window.beforeNav = 1')
expect(await browser.elementByCss('#router-pathname').text()).toBe(
'/locale-false'
)
expect(await browser.elementByCss('#router-as-path').text()).toBe(
'/locale-false?nextLocale=nl'
)
expect(await browser.elementByCss('#router-locale').text()).toBe('en-US')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({ nextLocale: 'nl' })
expect(await browser.elementByCss('html').getAttribute('lang')).toBe(
'en-US'
)
await browser.elementByCss('#to-fallback-first').click()
await browser.waitForElementByCss('#gsp')
expect(await browser.elementByCss('#router-pathname').text()).toBe(
'/gsp/fallback/[slug]'
)
expect(await browser.elementByCss('#router-as-path').text()).toBe(
'/gsp/fallback/first'
)
expect(await browser.elementByCss('#router-locale').text()).toBe('nl')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({ slug: 'first' })
expect(await browser.elementByCss('html').getAttribute('lang')).toBe('nl')
let parsedUrl = new URL(await browser.eval('window.location.href'))
expect(parsedUrl.pathname).toBe(`${ctx.basePath}/nl/gsp/fallback/first`)
expect(Object.fromEntries(parsedUrl.searchParams.entries())).toEqual({})
await browser.eval('window.history.back()')
await browser.waitForElementByCss('#links')
expect(await browser.elementByCss('#router-pathname').text()).toBe(
'/locale-false'
)
expect(await browser.elementByCss('#router-as-path').text()).toBe(
'/locale-false?nextLocale=nl'
)
expect(await browser.elementByCss('#router-locale').text()).toBe('en-US')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({ nextLocale: 'nl' })
expect(await browser.elementByCss('html').getAttribute('lang')).toBe(
'en-US'
)
parsedUrl = new URL(await browser.eval('window.location.href'))
expect(parsedUrl.pathname).toBe(`${ctx.basePath}/locale-false`)
expect(Object.fromEntries(parsedUrl.searchParams.entries())).toEqual({
nextLocale: 'nl',
})
await browser.eval('window.history.forward()')
await browser.waitForElementByCss('#gsp')
expect(await browser.elementByCss('#router-pathname').text()).toBe(
'/gsp/fallback/[slug]'
)
expect(await browser.elementByCss('#router-as-path').text()).toBe(
'/gsp/fallback/first'
)
expect(await browser.elementByCss('#router-locale').text()).toBe('nl')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({ slug: 'first' })
expect(await browser.elementByCss('html').getAttribute('lang')).toBe('nl')
parsedUrl = new URL(await browser.eval('window.location.href'))
expect(parsedUrl.pathname).toBe(`${ctx.basePath}/nl/gsp/fallback/first`)
expect(Object.fromEntries(parsedUrl.searchParams.entries())).toEqual({})
expect(await browser.eval('window.beforeNav')).toBe(1)
expect(await browser.eval('window.caughtWarns')).toEqual([])
})
it('should update asPath on the client correctly', async () => {
for (const check of ['en', 'En']) {
const browser = await webdriver(ctx.appPort, `${ctx.basePath}/${check}`)
expect(await browser.elementByCss('html').getAttribute('lang')).toBe('en')
expect(await browser.elementByCss('#router-locale').text()).toBe('en')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(await browser.elementByCss('#router-as-path').text()).toBe('/')
expect(await browser.elementByCss('#router-pathname').text()).toBe('/')
}
})
if (!ctx.isDev) {
it('should handle fallback correctly after generating', async () => {
const browser = await webdriver(
ctx.appPort,
`${ctx.basePath}/en/gsp/fallback/hello-fallback`
)
// wait for the fallback to be generated/stored to ISR cache
browser.waitForElementByCss('#gsp')
// now make sure we're serving the previously generated file from the cache
const html = await renderViaHTTP(
ctx.appPort,
`${ctx.basePath}/en/gsp/fallback/hello-fallback`
)
const $ = cheerio.load(html)
expect($('#gsp').text()).toBe('gsp page')
expect($('#router-locale').text()).toBe('en')
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect($('#router-pathname').text()).toBe('/gsp/fallback/[slug]')
expect($('#router-as-path').text()).toBe('/gsp/fallback/hello-fallback')
})
}
it('should use correct default locale for locale domains', async () => {
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath || '/'}`,
undefined,
{
headers: {
host: 'example.do',
},
}
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
expect($('html').attr('lang')).toBe('do')
expect($('#router-locale').text()).toBe('do')
expect($('#router-as-path').text()).toBe('/')
expect($('#router-pathname').text()).toBe('/')
// expect(JSON.parse($('#router-locales').text())).toEqual(['fr','fr-BE'])
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
const res2 = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath || '/'}`,
undefined,
{
headers: {
host: 'example.com',
},
}
)
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('html').attr('lang')).toBe('go')
expect($2('#router-locale').text()).toBe('go')
expect($2('#router-as-path').text()).toBe('/')
expect($2('#router-pathname').text()).toBe('/')
// expect(JSON.parse($2('#router-locales').text())).toEqual(['nl-BE','fr-BE'])
expect(JSON.parse($2('#router-locales').text())).toEqual(locales)
})
it('should not strip locale prefix for default locale with locale domains', async () => {
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath}/do`,
undefined,
{
headers: {
host: 'example.do',
},
redirect: 'manual',
}
)
expect(res.status).toBe(200)
// const result = new URL(res.headers.get('location'), true)
// expect(result.pathname).toBe('/')
// expect(result.query).toEqual({})
const res2 = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath}/go`,
undefined,
{
headers: {
host: 'example.com',
},
redirect: 'manual',
}
)
expect(res2.status).toBe(200)
// const result2 = new URL(res2.headers.get('location'), true)
// expect(result2.pathname).toBe('/')
// expect(result2.query).toEqual({})
})
// ('should set locale cookie when removing default locale and accept-lang doesnt match', async () => {
// const res = await fetchViaHTTP(ctx.appPort, '/en-US', undefined, {
// headers: {
// 'accept-language': 'nl',
// },
// redirect: 'manual',
// })
// expect(res.status).toBe(307)
// const parsedUrl = new URL(res.headers.get('location'), true)
// expect(parsedUrl.pathname).toBe('/')
// expect(parsedUrl.query).toEqual({})
// expect(res.headers.get('set-cookie')).toContain('NEXT_LOCALE=en-US')
// })
it('should not redirect to accept-lang preferred locale with locale cookie', async () => {
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath || '/'}`,
undefined,
{
headers: {
'accept-language': 'nl',
cookie: 'NEXT_LOCALE=en-US',
},
redirect: 'manual',
}
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
expect($('#router-locale').text()).toBe('en-US')
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect($('html').attr('lang')).toBe('en-US')
expect($('#router-pathname').text()).toBe('/')
expect($('#router-as-path').text()).toBe('/')
})
it('should redirect to correct locale domain', async () => {
const checks = [
// test domain, locale prefix, redirect result
// ['example.be', 'nl-BE', 'http://example.be/'],
['example.com', 'do', 'http://example.do/'],
['example.do', 'go', 'https://example.com/'],
// ['example.fr', 'fr', 'http://example.fr/'],
]
for (const check of checks) {
const [domain, locale, location] = check
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath || '/'}`,
undefined,
{
headers: {
host: domain,
'accept-language': locale,
},
redirect: 'manual',
}
)
expect(res.status).toBe(307)
expect(res.headers.get('location')).toBe(location)
}
})
describe('should handle locales with domain', () => {
const domainItems = [
{
// used for testing, this should not be needed in most cases
// as production domains should always use https
http: true,
domain: 'example.do',
defaultLocale: 'do',
locales: ['do-BE'],
},
{
domain: 'example.com',
defaultLocale: 'go',
locales: ['go-BE'],
},
]
// For each domain, check that the locale is handled correctly.
describe.each(domainItems)('for domain $domain', ({ domain }) => {
// For each locale in all the domains, check that the locale is handled
// correctly.
it.each(domainItems.reduce((prev, cur) => [...prev, ...cur.locales], []))(
'should handle locale %s',
async (locale) => {
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath || '/'}`,
undefined,
{
headers: {
host: domain,
'accept-language': locale,
},
redirect: 'manual',
}
)
const expectedDomainItem = domainItems.find(
(item) =>
item.defaultLocale === locale || item.locales.includes(locale)
)
const shouldRedirect =
expectedDomainItem.domain !== domain ||
locale !== expectedDomainItem.defaultLocale
if (shouldRedirect) {
expect(res.status).toBe(307)
} else {
expect(res.status).toBe(200)
}
if (shouldRedirect) {
const parsedUrl = new URL(res.headers.get('location'))
const expectedPathname = `/${
expectedDomainItem.defaultLocale === locale ? '' : locale
}`
expect(parsedUrl.pathname).toBe(expectedPathname)
expect(
Object.fromEntries(parsedUrl.searchParams.entries())
).toEqual({})
expect(parsedUrl.hostname).toBe(expectedDomainItem.domain)
} else {
const html = await res.text()
const $ = cheerio.load(html)
expect($('html').attr('lang')).toBe(locale)
expect($('#router-locale').text()).toBe(locale)
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
// this will not be the domain's defaultLocale since we don't
// generate a prerendered version for each locale domain currently
expect($('#router-default-locale').text()).toBe('en-US')
}
}
)
})
})
it('should provide defaultLocale correctly for locale domain', async () => {
for (const { host, locale } of [
{ host: 'example.do', locale: 'do' },
{ host: 'example.com', locale: 'go' },
]) {
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath}/gssp`,
undefined,
{
redirect: 'manual',
headers: {
host,
},
}
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
expect($('#router-locale').text()).toBe(locale)
expect($('#router-default-locale').text()).toBe(locale)
expect(JSON.parse($('#props').text())).toEqual({
defaultLocale: locale,
locale,
locales,
query: {},
})
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
}
})
it('should generate fallbacks with all locales', async () => {
for (const locale of nonDomainLocales) {
const html = await renderViaHTTP(
ctx.appPort,
`${ctx.basePath}/${locale}/gsp/fallback/${Math.random()}`
)
const $ = cheerio.load(html)
expect($('html').attr('lang')).toBe(locale)
}
})
it('should generate auto-export page with all locales', async () => {
for (const locale of nonDomainLocales) {
const html = await renderViaHTTP(ctx.appPort, `${ctx.basePath}/${locale}`)
const $ = cheerio.load(html)
expect($('html').attr('lang')).toBe(locale)
expect($('#router-locale').text()).toBe(locale)
expect($('#router-as-path').text()).toBe('/')
expect($('#router-pathname').text()).toBe('/')
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
const html2 = await renderViaHTTP(
ctx.appPort,
`${ctx.basePath}/${locale}/auto-export`
)
const $2 = cheerio.load(html2)
expect($2('html').attr('lang')).toBe(locale)
expect($2('#router-locale').text()).toBe(locale)
expect($2('#router-as-path').text()).toBe('/auto-export')
expect($2('#router-pathname').text()).toBe('/auto-export')
expect(JSON.parse($2('#router-locales').text())).toEqual(locales)
}
})
it('should generate non-dynamic GSP page with all locales', async () => {
for (const locale of nonDomainLocales) {
const html = await renderViaHTTP(
ctx.appPort,
`${ctx.basePath}/${locale}/gsp`
)
const $ = cheerio.load(html)
expect($('html').attr('lang')).toBe(locale)
expect($('#router-locale').text()).toBe(locale)
expect($('#router-as-path').text()).toBe('/gsp')
expect($('#router-pathname').text()).toBe('/gsp')
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
// make sure locale is case-insensitive
const html2 = await renderViaHTTP(
ctx.appPort,
`${ctx.basePath}/${locale.toUpperCase()}/gsp`
)
const $2 = cheerio.load(html2)
expect($2('html').attr('lang')).toBe(locale)
expect($2('#router-locale').text()).toBe(locale)
expect($2('#router-as-path').text()).toBe('/gsp')
expect($2('#router-pathname').text()).toBe('/gsp')
expect(JSON.parse($2('#router-locales').text())).toEqual(locales)
}
})
if (!ctx.isDev) {
it('should not output GSP pages that returned notFound', async () => {
const skippedLocales = ['en', 'nl']
for (const locale of nonDomainLocales) {
const pagePath = join(ctx.buildPagesDir, locale, 'not-found.html')
const dataPath = join(ctx.buildPagesDir, locale, 'not-found.json')
console.log(pagePath)
expect(fs.existsSync(pagePath)).toBe(!skippedLocales.includes(locale))
expect(fs.existsSync(dataPath)).toBe(!skippedLocales.includes(locale))
}
})
}
it('should 404 for GSP pages that returned notFound', async () => {
const skippedLocales = ['en', 'nl']
for (const locale of nonDomainLocales) {
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath}/${locale}/not-found`
)
expect(res.status).toBe(skippedLocales.includes(locale) ? 404 : 200)
if (skippedLocales.includes(locale)) {
const browser = await webdriver(
ctx.appPort,
`${ctx.basePath}/${locale}/not-found`
)
expect(await browser.elementByCss('html').getAttribute('lang')).toBe(
locale
)
expect(
await browser.eval('document.documentElement.innerHTML')
).toContain('This page could not be found')
const props = JSON.parse(await browser.elementByCss('#props').text())
expect(props.is404).toBe(true)
expect(props.locale).toBe(locale)
const parsedUrl = new URL(await browser.eval('window.location.href'))
expect(parsedUrl.pathname).toBe(`${ctx.basePath}/${locale}/not-found`)
expect(Object.fromEntries(parsedUrl.searchParams.entries())).toEqual({})
}
}
})
it('should transition on client properly for page that starts with locale', async () => {
const browser = await webdriver(ctx.appPort, `${ctx.basePath}/fr`)
await browser.eval(`(function() {
window.beforeNav = 1
window.next.router.push('/frank')
})()`)
await browser.waitForElementByCss('#frank')
expect(await browser.elementByCss('#router-locale').text()).toBe('fr')
expect(await browser.elementByCss('#router-default-locale').text()).toBe(
'en-US'
)
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({})
expect(await browser.elementByCss('#router-pathname').text()).toBe('/frank')
expect(await browser.elementByCss('#router-as-path').text()).toBe('/frank')
expect(
new URL(await browser.eval(() => window.location.href)).pathname
).toBe(`${ctx.basePath}/fr/frank`)
expect(await browser.eval('window.beforeNav')).toBe(1)
})
it('should 404 for GSP that returned notFound on client-transition', async () => {
const browser = await webdriver(ctx.appPort, `${ctx.basePath}/en`)
await browser.eval(`(function() {
window.beforeNav = 1
window.next.router.push('/not-found')
})()`)
await browser.waitForElementByCss('h1')
expect(await browser.elementByCss('html').getAttribute('lang')).toBe('en')
expect(await browser.elementByCss('html').text()).toContain(
'This page could not be found'
)
const props = JSON.parse(await browser.elementByCss('#props').text())
expect(props.is404).toBe(true)
expect(props.locale).toBe('en')
expect(await browser.eval('window.beforeNav')).toBe(1)
})
it('should render 404 for fallback page that returned 404 on client transition', async () => {
const browser = await webdriver(ctx.appPort, `${ctx.basePath}/en`, {
retryWaitHydration: true,
})
await browser.eval(`(function() {
next.router.push('/not-found/fallback/first')
})()`)
await browser.waitForElementByCss('h1')
await browser.eval('window.beforeNav = 1')
expect(await browser.elementByCss('html').text()).toContain(
'This page could not be found'
)
const props = JSON.parse(await browser.elementByCss('#props').text())
expect(props.is404).toBe(true)
expect(props.locale).toBe('en')
expect(await browser.elementByCss('html').getAttribute('lang')).toBe('en')
const parsedUrl = new URL(await browser.eval('window.location.href'))
expect(parsedUrl.pathname).toBe(
`${ctx.basePath}/en/not-found/fallback/first`
)
expect(Object.fromEntries(parsedUrl.searchParams.entries())).toEqual({})
if (ctx.isDev) {
// make sure page doesn't reload un-necessarily in development
await waitFor(10 * 1000)
}
expect(await browser.eval('window.beforeNav')).toBe(1)
})
it('should render 404 for fallback page that returned 404', async () => {
const browser = await webdriver(
ctx.appPort,
`${ctx.basePath}/en/not-found/fallback/first`,
{
retryWaitHydration: true,
}
)
await browser.waitForElementByCss('h1')
await browser.eval('window.beforeNav = 1')
expect(await browser.elementByCss('html').text()).toContain(
'This page could not be found'
)
const props = JSON.parse(await browser.elementByCss('#props').text())
expect(props.is404).toBe(true)
expect(props.locale).toBe('en')
expect(await browser.elementByCss('html').getAttribute('lang')).toBe('en')
const parsedUrl = new URL(await browser.eval('window.location.href'))
expect(parsedUrl.pathname).toBe(
`${ctx.basePath}/en/not-found/fallback/first`
)
expect(Object.fromEntries(parsedUrl.searchParams.entries())).toEqual({})
if (ctx.isDev) {
// make sure page doesn't reload un-necessarily in development
await waitFor(10 * 1000)
}
expect(await browser.eval('window.beforeNav')).toBe(1)
})
it('should render 404 for blocking fallback page that returned 404 on client transition', async () => {
const browser = await webdriver(ctx.appPort, `${ctx.basePath}/en`, {
retryWaitHydration: true,
})
await browser.eval(`(function() {
next.router.push('/not-found/blocking-fallback/first')
})()`)
await browser.waitForElementByCss('h1')
await browser.eval('window.beforeNav = 1')
expect(await browser.elementByCss('html').text()).toContain(
'This page could not be found'
)
const props = JSON.parse(await browser.elementByCss('#props').text())
expect(props.is404).toBe(true)
expect(props.locale).toBe('en')
expect(await browser.elementByCss('html').getAttribute('lang')).toBe('en')
const parsedUrl = new URL(await browser.eval('window.location.href'))
expect(parsedUrl.pathname).toBe(
`${ctx.basePath}/en/not-found/blocking-fallback/first`
)
expect(Object.fromEntries(parsedUrl.searchParams.entries())).toEqual({})
if (ctx.isDev) {
// make sure page doesn't reload un-necessarily in development
await waitFor(10 * 1000)
}
expect(await browser.eval('window.beforeNav')).toBe(1)
})
it('should render 404 for blocking fallback page that returned 404', async () => {
const browser = await webdriver(
ctx.appPort,
`${ctx.basePath}/en/not-found/blocking-fallback/first`,
{
retryWaitHydration: true,
}
)
await browser.waitForElementByCss('h1')
await browser.eval('window.beforeNav = 1')
expect(await browser.elementByCss('html').text()).toContain(
'This page could not be found'
)
const props = JSON.parse(await browser.elementByCss('#props').text())
expect(props.is404).toBe(true)
expect(props.locale).toBe('en')
expect(await browser.elementByCss('html').getAttribute('lang')).toBe('en')
const parsedUrl = new URL(await browser.eval('window.location.href'))
expect(parsedUrl.pathname).toBe(
`${ctx.basePath}/en/not-found/blocking-fallback/first`
)
expect(Object.fromEntries(parsedUrl.searchParams.entries())).toEqual({})
if (ctx.isDev) {
// make sure page doesn't reload un-necessarily in development
await waitFor(10 * 1000)
}
expect(await browser.eval('window.beforeNav')).toBe(1)
})
it('should not remove locale prefix for default locale', async () => {
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath}/en-US`,
undefined,
{
redirect: 'manual',
headers: {
'Accept-Language': 'en-US;q=0.9',
},
}
)
expect(res.status).toBe(200)
// const parsedUrl = new URL(res.headers.get('location'), true)
// expect(parsedUrl.pathname).toBe('/')
// expect(parsedUrl.query).toEqual({})
// make sure locale is case-insensitive
const res2 = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath}/eN-Us`,
undefined,
{
redirect: 'manual',
headers: {
'Accept-Language': 'en-US;q=0.9',
},
}
)
expect(res2.status).toBe(200)
// const parsedUrl2 = new URL(res.headers.get('location'), true)
// expect(parsedUrl2.pathname).toBe('/')
// expect(parsedUrl2.query).toEqual({})
})
it('should load getStaticProps page correctly SSR (default locale no prefix)', async () => {
const html = await renderViaHTTP(ctx.appPort, `${ctx.basePath}/gsp`)
const $ = cheerio.load(html)
expect(JSON.parse($('#props').text())).toEqual({
locale: 'en-US',
locales,
defaultLocale: 'en-US',
})
expect($('#router-locale').text()).toBe('en-US')
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect($('html').attr('lang')).toBe('en-US')
})
it('should load getStaticProps fallback prerender page correctly SSR (default locale no prefix)', async () => {
const html = await renderViaHTTP(
ctx.appPort,
`${ctx.basePath}/gsp/fallback/first`
)
const $ = cheerio.load(html)
expect(JSON.parse($('#props').text())).toEqual({
locale: 'en-US',
locales,
params: {
slug: 'first',
},
defaultLocale: 'en-US',
})
expect(JSON.parse($('#router-query').text())).toEqual({
slug: 'first',
})
expect($('#router-locale').text()).toBe('en-US')
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect($('html').attr('lang')).toBe('en-US')
})
it('should load getStaticProps fallback non-prerender page correctly (default locale no prefix', async () => {
const browser = await webdriver(
ctx.appPort,
`${ctx.basePath}/gsp/fallback/another`
)
await browser.waitForElementByCss('#props')
expect(JSON.parse(await browser.elementByCss('#props').text())).toEqual({
locale: 'en-US',
locales,
params: {
slug: 'another',
},
defaultLocale: 'en-US',
})
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({
slug: 'another',
})
expect(await browser.elementByCss('#router-locale').text()).toBe('en-US')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
})
it('should redirect to locale prefixed route for /', async () => {
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath || '/'}`,
undefined,
{
redirect: 'manual',
headers: {
'Accept-Language': 'nl-NL,nl;q=0.9,en-US;q=0.8,en;q=0.7',
},
}
)
expect(res.status).toBe(307)
const parsedUrl = new URL(res.headers.get('location'))
expect(parsedUrl.pathname).toBe(`${ctx.basePath}/nl-NL`)
expect(Object.fromEntries(parsedUrl.searchParams.entries())).toEqual({})
const res2 = await fetchViaHTTP(
ctx.appPort,
'/',
{ hello: 'world' },
{
redirect: 'manual',
headers: {
'Accept-Language': 'en;q=0.9',
},
}
)
expect(res2.status).toBe(307)
const parsedUrl2 = new URL(res2.headers.get('location'))
expect(parsedUrl2.pathname).toBe(`${ctx.basePath}/en`)
expect(Object.fromEntries(parsedUrl2.searchParams.entries())).toEqual({
hello: 'world',
})
})
it('should use default locale for / without accept-language', async () => {
const res = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath || '/'}`,
undefined,
{
redirect: 'manual',
}
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
expect($('#router-locale').text()).toBe('en-US')
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect(JSON.parse($('#router-query').text())).toEqual({})
expect($('#router-pathname').text()).toBe('/')
expect($('#router-as-path').text()).toBe('/')
const res2 = await fetchViaHTTP(
ctx.appPort,
`${ctx.basePath || '/'}`,
{ hello: 'world' },
{
redirect: 'manual',
}
)
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#router-locale').text()).toBe('en-US')
expect(JSON.parse($2('#router-locales').text())).toEqual(locales)
// page is auto-export so query isn't hydrated until client
expect(JSON.parse($2('#router-query').text())).toEqual({})
expect($2('#router-pathname').text()).toBe('/')
// expect($2('#router-as-path').text()).toBe('/')
})
it('should load getStaticProps page correctly SSR', async () => {
const html = await renderViaHTTP(ctx.appPort, `${ctx.basePath}/en-US/gsp`)
const $ = cheerio.load(html)
expect(JSON.parse($('#props').text())).toEqual({
locale: 'en-US',
locales,
defaultLocale: 'en-US',
})
expect($('#router-locale').text()).toBe('en-US')
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect($('html').attr('lang')).toBe('en-US')
})
it('should load getStaticProps fallback prerender page correctly SSR', async () => {
const html = await renderViaHTTP(
ctx.appPort,
`${ctx.basePath}/en-US/gsp/fallback/first`
)
const $ = cheerio.load(html)
expect(JSON.parse($('#props').text())).toEqual({
locale: 'en-US',
locales,
params: {
slug: 'first',
},
defaultLocale: 'en-US',
})
expect(JSON.parse($('#router-query').text())).toEqual({
slug: 'first',
})
expect($('#router-locale').text()).toBe('en-US')
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect($('html').attr('lang')).toBe('en-US')
})
it('should load getStaticProps fallback non-prerender page correctly', async () => {
const browser = await webdriver(
ctx.appPort,
`${ctx.basePath}/en/gsp/fallback/another`
)
await browser.waitForElementByCss('#props')
expect(JSON.parse(await browser.elementByCss('#props').text())).toEqual({
locale: 'en',
locales,
params: {
slug: 'another',
},
defaultLocale: 'en-US',
})
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({
slug: 'another',
})
expect(await browser.elementByCss('#router-locale').text()).toBe('en')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(await browser.elementByCss('html').getAttribute('lang')).toBe('en')
})
it('should load getServerSideProps page correctly SSR (default locale no prefix)', async () => {
const html = await renderViaHTTP(ctx.appPort, `${ctx.basePath}/gssp`)
const $ = cheerio.load(html)
expect(JSON.parse($('#props').text())).toEqual({
locale: 'en-US',
locales,
query: {},
defaultLocale: 'en-US',
})
expect($('#router-locale').text()).toBe('en-US')
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect(JSON.parse($('#router-query').text())).toEqual({})
expect($('html').attr('lang')).toBe('en-US')
})
it('should navigate client side for default locale with no prefix', async () => {
const browser = await webdriver(ctx.appPort, `${ctx.basePath || '/'}`)
await addDefaultLocaleCookie(browser)
const checkIndexValues = async () => {
expect(await browser.elementByCss('#router-locale').text()).toBe('en-US')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({})
expect(await browser.elementByCss('#router-pathname').text()).toBe('/')
expect(await browser.elementByCss('#router-as-path').text()).toBe('/')
expect(
new URL(await browser.eval(() => window.location.href)).pathname
).toBe(`${ctx.basePath || '/'}`)
}
await checkIndexValues()
await browser.elementByCss('#to-another').click()
await browser.waitForElementByCss('#another')
expect(JSON.parse(await browser.elementByCss('#props').text())).toEqual({
locale: 'en-US',
locales,
defaultLocale: 'en-US',
})
expect(await browser.elementByCss('#router-locale').text()).toBe('en-US')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({})
expect(await browser.elementByCss('#router-pathname').text()).toBe(
'/another'
)
expect(await browser.elementByCss('#router-as-path').text()).toBe(
'/another'
)
expect(
new URL(await browser.eval(() => window.location.href)).pathname
).toBe(`${ctx.basePath}/another`)
await browser.elementByCss('#to-index').click()
await browser.waitForElementByCss('#index')
await checkIndexValues()
await browser.elementByCss('#to-gsp').click()
await browser.waitForElementByCss('#gsp')
expect(JSON.parse(await browser.elementByCss('#props').text())).toEqual({
locale: 'en-US',
locales,
defaultLocale: 'en-US',
})
expect(await browser.elementByCss('#router-locale').text()).toBe('en-US')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({})
expect(await browser.elementByCss('#router-pathname').text()).toBe('/gsp')
expect(await browser.elementByCss('#router-as-path').text()).toBe('/gsp')
expect(
new URL(await browser.eval(() => window.location.href)).pathname
).toBe(`${ctx.basePath}/gsp`)
await browser.elementByCss('#to-index').click()
await browser.waitForElementByCss('#index')
await checkIndexValues()
await browser.deleteCookies()
})
it('should load getStaticProps fallback non-prerender page another locale correctly', async () => {
const browser = await webdriver(
ctx.appPort,
`${ctx.basePath}/nl-NL/gsp/fallback/another`
)
await browser.waitForElementByCss('#props')
expect(JSON.parse(await browser.elementByCss('#props').text())).toEqual({
locale: 'nl-NL',
locales,
params: {
slug: 'another',
},
defaultLocale: 'en-US',
})
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({
slug: 'another',
})
expect(await browser.elementByCss('#router-locale').text()).toBe('nl-NL')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
})
it('should load getStaticProps non-fallback correctly', async () => {
const browser = await webdriver(
ctx.appPort,
`${ctx.basePath}/en-US/gsp/no-fallback/first`
)
await browser.waitForElementByCss('#props')
expect(JSON.parse(await browser.elementByCss('#props').text())).toEqual({
locale: 'en-US',
locales,
params: {
slug: 'first',
},
defaultLocale: 'en-US',
})
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({
slug: 'first',
})
expect(await browser.elementByCss('#router-locale').text()).toBe('en-US')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(await browser.elementByCss('html').getAttribute('lang')).toBe(
'en-US'
)
})
it('should load getStaticProps non-fallback correctly another locale', async () => {
const browser = await webdriver(
ctx.appPort,
`${ctx.basePath}/nl-NL/gsp/no-fallback/second`
)
await browser.waitForElementByCss('#props')
expect(JSON.parse(await browser.elementByCss('#props').text())).toEqual({
locale: 'nl-NL',
locales,
params: {
slug: 'second',
},
defaultLocale: 'en-US',
})
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({
slug: 'second',
})
expect(await browser.elementByCss('#router-locale').text()).toBe('nl-NL')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(await browser.elementByCss('html').getAttribute('lang')).toBe(
'nl-NL'
)
})
it('should load getStaticProps non-fallback correctly another locale via cookie', async () => {
const html = await renderViaHTTP(
ctx.appPort,
`${ctx.basePath}/nl-NL/gsp/no-fallback/second`,
{},
{
headers: {
cookie: 'NEXT_LOCALE=nl-NL',
},
}
)
const $ = cheerio.load(html)
expect(JSON.parse($('#props').text())).toEqual({
locale: 'nl-NL',
locales,
params: {
slug: 'second',
},
defaultLocale: 'en-US',
})
expect(JSON.parse($('#router-query').text())).toEqual({
slug: 'second',
})
expect($('#router-locale').text()).toBe('nl-NL')
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect($('html').attr('lang')).toBe('nl-NL')
})
it('should load getServerSideProps page correctly SSR', async () => {
const html = await renderViaHTTP(ctx.appPort, `${ctx.basePath}/en-US/gssp`)
const $ = cheerio.load(html)
expect(JSON.parse($('#props').text())).toEqual({
locale: 'en-US',
locales,
query: {},
defaultLocale: 'en-US',
})
expect($('#router-locale').text()).toBe('en-US')
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect(JSON.parse($('#router-query').text())).toEqual({})
expect($('html').attr('lang')).toBe('en-US')
const html2 = await renderViaHTTP(ctx.appPort, `${ctx.basePath}/nl-NL/gssp`)
const $2 = cheerio.load(html2)
expect(JSON.parse($2('#props').text())).toEqual({
locale: 'nl-NL',
locales,
query: {},
defaultLocale: 'en-US',
})
expect($2('#router-locale').text()).toBe('nl-NL')
expect(JSON.parse($2('#router-locales').text())).toEqual(locales)
expect(JSON.parse($2('#router-query').text())).toEqual({})
expect($2('html').attr('lang')).toBe('nl-NL')
})
it('should load dynamic getServerSideProps page correctly SSR', async () => {
const html = await renderViaHTTP(
ctx.appPort,
`${ctx.basePath}/en-US/gssp/first`
)
const $ = cheerio.load(html)
expect(JSON.parse($('#props').text())).toEqual({
locale: 'en-US',
locales,
params: {
slug: 'first',
},
defaultLocale: 'en-US',
})
expect($('#router-locale').text()).toBe('en-US')
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'first' })
expect($('html').attr('lang')).toBe('en-US')
const html2 = await renderViaHTTP(
ctx.appPort,
`${ctx.basePath}/nl-NL/gssp/first`
)
const $2 = cheerio.load(html2)
expect(JSON.parse($2('#props').text())).toEqual({
locale: 'nl-NL',
locales,
params: {
slug: 'first',
},
defaultLocale: 'en-US',
})
expect($2('#router-locale').text()).toBe('nl-NL')
expect(JSON.parse($2('#router-locales').text())).toEqual(locales)
expect(JSON.parse($2('#router-query').text())).toEqual({ slug: 'first' })
expect($2('html').attr('lang')).toBe('nl-NL')
})
it('should navigate to another page and back correctly with locale', async () => {
const browser = await webdriver(ctx.appPort, `${ctx.basePath}/en`)
await browser.eval('window.beforeNav = "hi"')
await browser
.elementByCss('#to-another')
.click()
.waitForElementByCss('#another')
expect(await browser.elementByCss('#router-pathname').text()).toBe(
'/another'
)
expect(await browser.elementByCss('#router-as-path').text()).toBe(
'/another'
)
expect(await browser.elementByCss('#router-locale').text()).toBe('en')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(JSON.parse(await browser.elementByCss('#props').text())).toEqual({
locale: 'en',
locales,
defaultLocale: 'en-US',
})
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({})
expect(await browser.eval('window.beforeNav')).toBe('hi')
await browser.back().waitForElementByCss('#index')
expect(await browser.eval('window.beforeNav')).toBe('hi')
expect(await browser.elementByCss('#router-pathname').text()).toBe('/')
expect(await browser.elementByCss('#router-as-path').text()).toBe('/')
})
it('should navigate to getStaticProps page and back correctly with locale', async () => {
const browser = await webdriver(ctx.appPort, `${ctx.basePath}/en`)
await browser.eval('window.beforeNav = "hi"')
await browser.elementByCss('#to-gsp').click().waitForElementByCss('#gsp')
expect(await browser.elementByCss('#router-pathname').text()).toBe('/gsp')
expect(await browser.elementByCss('#router-as-path').text()).toBe('/gsp')
expect(await browser.elementByCss('#router-locale').text()).toBe('en')
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(JSON.parse(await browser.elementByCss('#props').text())).toEqual({
locale: 'en',
locales,
defaultLocale: 'en-US',
})
expect(
JSON.parse(await browser.elementByCss('#router-query').text())
).toEqual({})
expect(await browser.eval('window.beforeNav')).toBe('hi')
await browser.back().waitForElementByCss('#index')
expect(await browser.eval('window.beforeNav')).toBe('hi')
expect(await browser.elementByCss('#router-pathname').text()).toBe('/')
expect(await browser.elementByCss('#router-as-path').text()).toBe('/')
})
}