rsnext/test/e2e/app-dir/app-static/app-static.test.ts
Talha Zekeriya Durmuş 16131e2d49
Fix #46621 - include status code in cache (#47096)
Fixes #46621

As explained here https://github.com/vercel/next.js/issues/46621,

> Using fetch to retrieve data inside a page on server side, the
response status code is incorrect when simply loading/reloading the
page. It looks like nextjs is caching the response without taking into
account the status code.

In following code we do not add status code information to the cache. 

https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/patch-fetch.ts#L189-L206

However we are accessing this status code in 

https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/patch-fetch.ts#L247-L250

---------

Co-authored-by: JJ Kasper <jj@jjsweb.site>
2023-03-13 21:37:22 -07:00

1563 lines
56 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import globOrig from 'glob'
import cheerio from 'cheerio'
import { promisify } from 'util'
import { join } from 'path'
import { createNextDescribe } from 'e2e-utils'
import { check, fetchViaHTTP, normalizeRegEx, waitFor } from 'next-test-utils'
import stripAnsi from 'strip-ansi'
const glob = promisify(globOrig)
createNextDescribe(
'app-dir static/dynamic handling',
{
files: __dirname,
env: {
NEXT_DEBUG_BUILD: '1',
...(process.env.CUSTOM_CACHE_HANDLER
? {
CUSTOM_CACHE_HANDLER: process.env.CUSTOM_CACHE_HANDLER,
}
: {}),
},
},
({ next, isNextDev: isDev, isNextStart, isNextDeploy }) => {
let prerenderManifest
beforeAll(async () => {
if (isNextStart) {
prerenderManifest = JSON.parse(
await next.readFile('.next/prerender-manifest.json')
)
}
})
if (isDev) {
it('should error correctly for invalid params from generateStaticParams', async () => {
await next.patchFile(
'app/invalid/[slug]/page.js',
`
export function generateStaticParams() {
return [{slug: { invalid: true }}]
}
`
)
const html = await next.render('/invalid/first')
await next.deleteFile('app/invalid/[slug]/page.js')
expect(html).toContain(
'A required parameter (slug) was not provided as a string received object'
)
})
}
it('should include statusCode in cache', async () => {
const $ = await next.render$('/variable-revalidate/status-code')
const origData = JSON.parse($('#page-data').text())
expect(origData.status).toBe(404)
await check(async () => {
const new$ = await next.render$('/variable-revalidate/status-code')
const newData = JSON.parse(new$('#page-data').text())
expect(newData.status).toBe(origData.status)
expect(newData.text).not.toBe(origData.text)
return 'success'
}, 'success')
})
if (isNextStart) {
it('should output HTML/RSC files for static paths', async () => {
const files = (
await glob('**/*', {
cwd: join(next.testDir, '.next/server/app'),
})
)
.filter((file) => file.match(/.*\.(js|html|rsc)$/))
.map((file) => {
return file.replace(
/partial-gen-params-no-additional-([\w]{1,})\/([\w]{1,})\/([\d]{1,})/,
'partial-gen-params-no-additional-$1/$2/RAND'
)
})
expect(files).toEqual([
'(new)/custom/page.js',
'blog/[author]/[slug]/page.js',
'blog/[author]/page.js',
'blog/seb.html',
'blog/seb.rsc',
'blog/seb/second-post.html',
'blog/seb/second-post.rsc',
'blog/styfle.html',
'blog/styfle.rsc',
'blog/styfle/first-post.html',
'blog/styfle/first-post.rsc',
'blog/styfle/second-post.html',
'blog/styfle/second-post.rsc',
'blog/tim.html',
'blog/tim.rsc',
'blog/tim/first-post.html',
'blog/tim/first-post.rsc',
'dynamic-error/[id]/page.js',
'dynamic-no-gen-params-ssr/[slug]/page.js',
'dynamic-no-gen-params/[slug]/page.js',
'force-dynamic-no-prerender/[id]/page.js',
'force-static/[slug]/page.js',
'force-static/first.html',
'force-static/first.rsc',
'force-static/page.js',
'force-static/second.html',
'force-static/second.rsc',
'gen-params-dynamic-revalidate/[slug]/page.js',
'gen-params-dynamic-revalidate/one.html',
'gen-params-dynamic-revalidate/one.rsc',
'gen-params-dynamic/[slug]/page.js',
'hooks/use-pathname/[slug]/page.js',
'hooks/use-pathname/slug.html',
'hooks/use-pathname/slug.rsc',
'hooks/use-search-params.html',
'hooks/use-search-params.rsc',
'hooks/use-search-params/force-static.html',
'hooks/use-search-params/force-static.rsc',
'hooks/use-search-params/force-static/page.js',
'hooks/use-search-params/page.js',
'hooks/use-search-params/with-suspense.html',
'hooks/use-search-params/with-suspense.rsc',
'hooks/use-search-params/with-suspense/page.js',
'partial-gen-params-no-additional-lang/[lang]/[slug]/page.js',
'partial-gen-params-no-additional-lang/en/RAND.html',
'partial-gen-params-no-additional-lang/en/RAND.rsc',
'partial-gen-params-no-additional-lang/en/first.html',
'partial-gen-params-no-additional-lang/en/first.rsc',
'partial-gen-params-no-additional-lang/en/second.html',
'partial-gen-params-no-additional-lang/en/second.rsc',
'partial-gen-params-no-additional-lang/fr/RAND.html',
'partial-gen-params-no-additional-lang/fr/RAND.rsc',
'partial-gen-params-no-additional-lang/fr/first.html',
'partial-gen-params-no-additional-lang/fr/first.rsc',
'partial-gen-params-no-additional-lang/fr/second.html',
'partial-gen-params-no-additional-lang/fr/second.rsc',
'partial-gen-params-no-additional-slug/[lang]/[slug]/page.js',
'partial-gen-params-no-additional-slug/en/RAND.html',
'partial-gen-params-no-additional-slug/en/RAND.rsc',
'partial-gen-params-no-additional-slug/en/first.html',
'partial-gen-params-no-additional-slug/en/first.rsc',
'partial-gen-params-no-additional-slug/en/second.html',
'partial-gen-params-no-additional-slug/en/second.rsc',
'partial-gen-params-no-additional-slug/fr/RAND.html',
'partial-gen-params-no-additional-slug/fr/RAND.rsc',
'partial-gen-params-no-additional-slug/fr/first.html',
'partial-gen-params-no-additional-slug/fr/first.rsc',
'partial-gen-params-no-additional-slug/fr/second.html',
'partial-gen-params-no-additional-slug/fr/second.rsc',
'partial-gen-params/[lang]/[slug]/page.js',
'ssg-preview.html',
'ssg-preview.rsc',
'ssg-preview/[[...route]]/page.js',
'ssg-preview/test-2.html',
'ssg-preview/test-2.rsc',
'ssg-preview/test.html',
'ssg-preview/test.rsc',
'ssr-auto/cache-no-store/page.js',
'ssr-auto/fetch-revalidate-zero/page.js',
'ssr-forced/page.js',
'static-to-dynamic-error-forced/[id]/page.js',
'static-to-dynamic-error/[id]/page.js',
'variable-revalidate-edge/encoding/page.js',
'variable-revalidate-edge/no-store/page.js',
'variable-revalidate-edge/post-method-request/page.js',
'variable-revalidate-edge/post-method/page.js',
'variable-revalidate-edge/revalidate-3/page.js',
'variable-revalidate/authorization.html',
'variable-revalidate/authorization.rsc',
'variable-revalidate/authorization/page.js',
'variable-revalidate/cookie.html',
'variable-revalidate/cookie.rsc',
'variable-revalidate/cookie/page.js',
'variable-revalidate/encoding.html',
'variable-revalidate/encoding.rsc',
'variable-revalidate/encoding/page.js',
'variable-revalidate/no-store/page.js',
'variable-revalidate/post-method-request/page.js',
'variable-revalidate/post-method.html',
'variable-revalidate/post-method.rsc',
'variable-revalidate/post-method/page.js',
'variable-revalidate/revalidate-3.html',
'variable-revalidate/revalidate-3.rsc',
'variable-revalidate/revalidate-3/page.js',
'variable-revalidate/status-code.html',
'variable-revalidate/status-code.rsc',
'variable-revalidate/status-code/page.js',
])
})
it('should have correct prerender-manifest entries', async () => {
const curManifest = JSON.parse(JSON.stringify(prerenderManifest))
for (const key of Object.keys(curManifest.dynamicRoutes)) {
const item = curManifest.dynamicRoutes[key]
if (item.dataRouteRegex) {
item.dataRouteRegex = normalizeRegEx(item.dataRouteRegex)
}
if (item.routeRegex) {
item.routeRegex = normalizeRegEx(item.routeRegex)
}
}
for (const key of Object.keys(curManifest.routes)) {
const newKey = key.replace(
/partial-gen-params-no-additional-([\w]{1,})\/([\w]{1,})\/([\d]{1,})/,
'partial-gen-params-no-additional-$1/$2/RAND'
)
if (newKey !== key) {
const route = curManifest.routes[key]
delete curManifest.routes[key]
curManifest.routes[newKey] = {
...route,
dataRoute: `${newKey}.rsc`,
}
}
}
expect(curManifest.version).toBe(4)
expect(curManifest.routes).toEqual({
'/blog/tim': {
initialRevalidateSeconds: 10,
srcRoute: '/blog/[author]',
dataRoute: '/blog/tim.rsc',
},
'/blog/seb': {
initialRevalidateSeconds: 10,
srcRoute: '/blog/[author]',
dataRoute: '/blog/seb.rsc',
},
'/blog/styfle': {
initialRevalidateSeconds: 10,
srcRoute: '/blog/[author]',
dataRoute: '/blog/styfle.rsc',
},
'/blog/tim/first-post': {
initialRevalidateSeconds: false,
srcRoute: '/blog/[author]/[slug]',
dataRoute: '/blog/tim/first-post.rsc',
},
'/blog/seb/second-post': {
initialRevalidateSeconds: false,
srcRoute: '/blog/[author]/[slug]',
dataRoute: '/blog/seb/second-post.rsc',
},
'/blog/styfle/first-post': {
initialRevalidateSeconds: false,
srcRoute: '/blog/[author]/[slug]',
dataRoute: '/blog/styfle/first-post.rsc',
},
'/blog/styfle/second-post': {
initialRevalidateSeconds: false,
srcRoute: '/blog/[author]/[slug]',
dataRoute: '/blog/styfle/second-post.rsc',
},
'/hooks/use-pathname/slug': {
dataRoute: '/hooks/use-pathname/slug.rsc',
initialRevalidateSeconds: false,
srcRoute: '/hooks/use-pathname/[slug]',
},
'/hooks/use-search-params': {
dataRoute: '/hooks/use-search-params.rsc',
initialRevalidateSeconds: false,
srcRoute: '/hooks/use-search-params',
},
'/hooks/use-search-params/force-static': {
dataRoute: '/hooks/use-search-params/force-static.rsc',
initialRevalidateSeconds: false,
srcRoute: '/hooks/use-search-params/force-static',
},
'/hooks/use-search-params/with-suspense': {
dataRoute: '/hooks/use-search-params/with-suspense.rsc',
initialRevalidateSeconds: false,
srcRoute: '/hooks/use-search-params/with-suspense',
},
'/partial-gen-params-no-additional-lang/en/RAND': {
dataRoute: '/partial-gen-params-no-additional-lang/en/RAND.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-lang/[lang]/[slug]',
},
'/partial-gen-params-no-additional-lang/en/first': {
dataRoute: '/partial-gen-params-no-additional-lang/en/first.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-lang/[lang]/[slug]',
},
'/partial-gen-params-no-additional-lang/en/second': {
dataRoute: '/partial-gen-params-no-additional-lang/en/second.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-lang/[lang]/[slug]',
},
'/partial-gen-params-no-additional-lang/fr/RAND': {
dataRoute: '/partial-gen-params-no-additional-lang/fr/RAND.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-lang/[lang]/[slug]',
},
'/partial-gen-params-no-additional-lang/fr/first': {
dataRoute: '/partial-gen-params-no-additional-lang/fr/first.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-lang/[lang]/[slug]',
},
'/partial-gen-params-no-additional-lang/fr/second': {
dataRoute: '/partial-gen-params-no-additional-lang/fr/second.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-lang/[lang]/[slug]',
},
'/partial-gen-params-no-additional-slug/en/RAND': {
dataRoute: '/partial-gen-params-no-additional-slug/en/RAND.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-slug/[lang]/[slug]',
},
'/partial-gen-params-no-additional-slug/en/first': {
dataRoute: '/partial-gen-params-no-additional-slug/en/first.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-slug/[lang]/[slug]',
},
'/partial-gen-params-no-additional-slug/en/second': {
dataRoute: '/partial-gen-params-no-additional-slug/en/second.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-slug/[lang]/[slug]',
},
'/partial-gen-params-no-additional-slug/fr/RAND': {
dataRoute: '/partial-gen-params-no-additional-slug/fr/RAND.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-slug/[lang]/[slug]',
},
'/partial-gen-params-no-additional-slug/fr/first': {
dataRoute: '/partial-gen-params-no-additional-slug/fr/first.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-slug/[lang]/[slug]',
},
'/partial-gen-params-no-additional-slug/fr/second': {
dataRoute: '/partial-gen-params-no-additional-slug/fr/second.rsc',
initialRevalidateSeconds: false,
srcRoute: '/partial-gen-params-no-additional-slug/[lang]/[slug]',
},
'/force-static/first': {
dataRoute: '/force-static/first.rsc',
initialRevalidateSeconds: false,
srcRoute: '/force-static/[slug]',
},
'/force-static/second': {
dataRoute: '/force-static/second.rsc',
initialRevalidateSeconds: false,
srcRoute: '/force-static/[slug]',
},
'/gen-params-dynamic-revalidate/one': {
dataRoute: '/gen-params-dynamic-revalidate/one.rsc',
initialRevalidateSeconds: 3,
srcRoute: '/gen-params-dynamic-revalidate/[slug]',
},
'/ssg-preview': {
dataRoute: '/ssg-preview.rsc',
initialRevalidateSeconds: false,
srcRoute: '/ssg-preview/[[...route]]',
},
'/ssg-preview/test': {
dataRoute: '/ssg-preview/test.rsc',
initialRevalidateSeconds: false,
srcRoute: '/ssg-preview/[[...route]]',
},
'/ssg-preview/test-2': {
dataRoute: '/ssg-preview/test-2.rsc',
initialRevalidateSeconds: false,
srcRoute: '/ssg-preview/[[...route]]',
},
'/variable-revalidate/authorization': {
dataRoute: '/variable-revalidate/authorization.rsc',
initialRevalidateSeconds: 10,
srcRoute: '/variable-revalidate/authorization',
},
'/variable-revalidate/cookie': {
dataRoute: '/variable-revalidate/cookie.rsc',
initialRevalidateSeconds: 3,
srcRoute: '/variable-revalidate/cookie',
},
'/variable-revalidate/encoding': {
dataRoute: '/variable-revalidate/encoding.rsc',
initialRevalidateSeconds: 3,
srcRoute: '/variable-revalidate/encoding',
},
'/variable-revalidate/post-method': {
dataRoute: '/variable-revalidate/post-method.rsc',
initialRevalidateSeconds: 10,
srcRoute: '/variable-revalidate/post-method',
},
'/variable-revalidate/revalidate-3': {
dataRoute: '/variable-revalidate/revalidate-3.rsc',
initialRevalidateSeconds: 3,
srcRoute: '/variable-revalidate/revalidate-3',
},
'/variable-revalidate/status-code': {
dataRoute: '/variable-revalidate/status-code.rsc',
initialRevalidateSeconds: 3,
srcRoute: '/variable-revalidate/status-code',
},
})
expect(curManifest.dynamicRoutes).toEqual({
'/blog/[author]/[slug]': {
routeRegex: normalizeRegEx('^/blog/([^/]+?)/([^/]+?)(?:/)?$'),
dataRoute: '/blog/[author]/[slug].rsc',
fallback: null,
dataRouteRegex: normalizeRegEx('^/blog/([^/]+?)/([^/]+?)\\.rsc$'),
},
'/blog/[author]': {
dataRoute: '/blog/[author].rsc',
dataRouteRegex: normalizeRegEx('^\\/blog\\/([^\\/]+?)\\.rsc$'),
fallback: false,
routeRegex: normalizeRegEx('^\\/blog\\/([^\\/]+?)(?:\\/)?$'),
},
'/dynamic-error/[id]': {
dataRoute: '/dynamic-error/[id].rsc',
dataRouteRegex: normalizeRegEx(
'^\\/dynamic\\-error\\/([^\\/]+?)\\.rsc$'
),
fallback: null,
routeRegex: normalizeRegEx(
'^\\/dynamic\\-error\\/([^\\/]+?)(?:\\/)?$'
),
},
'/gen-params-dynamic-revalidate/[slug]': {
dataRoute: '/gen-params-dynamic-revalidate/[slug].rsc',
dataRouteRegex: normalizeRegEx(
'^\\/gen\\-params\\-dynamic\\-revalidate\\/([^\\/]+?)\\.rsc$'
),
fallback: null,
routeRegex: normalizeRegEx(
'^\\/gen\\-params\\-dynamic\\-revalidate\\/([^\\/]+?)(?:\\/)?$'
),
},
'/hooks/use-pathname/[slug]': {
dataRoute: '/hooks/use-pathname/[slug].rsc',
dataRouteRegex: normalizeRegEx(
'^\\/hooks\\/use\\-pathname\\/([^\\/]+?)\\.rsc$'
),
fallback: null,
routeRegex: normalizeRegEx(
'^\\/hooks\\/use\\-pathname\\/([^\\/]+?)(?:\\/)?$'
),
},
'/partial-gen-params-no-additional-lang/[lang]/[slug]': {
dataRoute:
'/partial-gen-params-no-additional-lang/[lang]/[slug].rsc',
dataRouteRegex: normalizeRegEx(
'^\\/partial\\-gen\\-params\\-no\\-additional\\-lang\\/([^\\/]+?)\\/([^\\/]+?)\\.rsc$'
),
fallback: false,
routeRegex: normalizeRegEx(
'^\\/partial\\-gen\\-params\\-no\\-additional\\-lang\\/([^\\/]+?)\\/([^\\/]+?)(?:\\/)?$'
),
},
'/partial-gen-params-no-additional-slug/[lang]/[slug]': {
dataRoute:
'/partial-gen-params-no-additional-slug/[lang]/[slug].rsc',
dataRouteRegex: normalizeRegEx(
'^\\/partial\\-gen\\-params\\-no\\-additional\\-slug\\/([^\\/]+?)\\/([^\\/]+?)\\.rsc$'
),
fallback: false,
routeRegex: normalizeRegEx(
'^\\/partial\\-gen\\-params\\-no\\-additional\\-slug\\/([^\\/]+?)\\/([^\\/]+?)(?:\\/)?$'
),
},
'/partial-gen-params/[lang]/[slug]': {
dataRoute: '/partial-gen-params/[lang]/[slug].rsc',
dataRouteRegex: normalizeRegEx(
'^\\/partial\\-gen\\-params\\/([^\\/]+?)\\/([^\\/]+?)\\.rsc$'
),
fallback: null,
routeRegex: normalizeRegEx(
'^\\/partial\\-gen\\-params\\/([^\\/]+?)\\/([^\\/]+?)(?:\\/)?$'
),
},
'/force-static/[slug]': {
dataRoute: '/force-static/[slug].rsc',
dataRouteRegex: normalizeRegEx(
'^\\/force\\-static\\/([^\\/]+?)\\.rsc$'
),
fallback: null,
routeRegex: normalizeRegEx(
'^\\/force\\-static\\/([^\\/]+?)(?:\\/)?$'
),
},
'/ssg-preview/[[...route]]': {
dataRoute: '/ssg-preview/[[...route]].rsc',
dataRouteRegex: normalizeRegEx(
'^\\/ssg\\-preview(?:\\/(.+?))?\\.rsc$'
),
fallback: null,
routeRegex: normalizeRegEx(
'^\\/ssg\\-preview(?:\\/(.+?))?(?:\\/)?$'
),
},
'/static-to-dynamic-error-forced/[id]': {
dataRoute: '/static-to-dynamic-error-forced/[id].rsc',
dataRouteRegex: normalizeRegEx(
'^\\/static\\-to\\-dynamic\\-error\\-forced\\/([^\\/]+?)\\.rsc$'
),
fallback: null,
routeRegex: normalizeRegEx(
'^\\/static\\-to\\-dynamic\\-error\\-forced\\/([^\\/]+?)(?:\\/)?$'
),
},
})
})
it('should output debug info for static bailouts', async () => {
const cleanedOutput = stripAnsi(next.cliOutput)
expect(cleanedOutput).toContain(
'Static generation failed due to dynamic usage on /force-static, reason: headers'
)
expect(cleanedOutput).toContain(
'Static generation failed due to dynamic usage on /ssr-auto/cache-no-store, reason: no-store fetch'
)
})
}
if (isDev) {
it('should bypass fetch cache with cache-control: no-cache', async () => {
const res = await fetchViaHTTP(
next.url,
'/variable-revalidate/revalidate-3'
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const layoutData = $('#layout-data').text()
const pageData = $('#page-data').text()
const res2 = await fetchViaHTTP(
next.url,
'/variable-revalidate/revalidate-3',
undefined,
{
headers: {
'cache-control': 'no-cache',
},
}
)
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#layout-data').text()).not.toBe(layoutData)
expect($2('#page-data').text()).not.toBe(pageData)
})
} else {
it('should not error with dynamic server usage with force-static', async () => {
const res = await next.fetch(
'/static-to-dynamic-error-forced/static-bailout-1'
)
const outputIndex = next.cliOutput.length
const html = await res.text()
expect(res.status).toBe(200)
expect(html).toContain('/static-to-dynamic-error-forced')
expect(html).toMatch(/id:.*?static-bailout-1/)
if (isNextStart) {
expect(stripAnsi(next.cliOutput).substring(outputIndex)).not.toMatch(
/Page changed from static to dynamic at runtime \/static-to-dynamic-error-forced\/static-bailout-1, reason: cookies/
)
}
})
it('should properly error when dynamic = "error" page uses dynamic', async () => {
const res = await next.fetch('/dynamic-error/static-bailout-1')
const outputIndex = next.cliOutput.length
expect(res.status).toBe(500)
if (isNextStart) {
expect(stripAnsi(next.cliOutput).substring(outputIndex)).not.toMatch(
/Page with dynamic = "error" encountered dynamic data method on \/dynamic-error\/static-bailout-1/
)
}
})
}
it('should handle partial-gen-params with default dynamicParams correctly', async () => {
const res = await next.fetch('/partial-gen-params/en/first')
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const params = JSON.parse($('#params').text())
expect(params).toEqual({ lang: 'en', slug: 'first' })
})
it('should handle partial-gen-params with layout dynamicParams = false correctly', async () => {
for (const { path, status, params } of [
// these checks don't work with custom memory only
// cache handler
...(process.env.CUSTOM_CACHE_HANDLER
? []
: [
{
path: '/partial-gen-params-no-additional-lang/en/first',
status: 200,
params: { lang: 'en', slug: 'first' },
},
]),
{
path: '/partial-gen-params-no-additional-lang/de/first',
status: 404,
params: {},
},
{
path: '/partial-gen-params-no-additional-lang/en/non-existent',
status: 404,
params: {},
},
]) {
const res = await next.fetch(path)
expect(res.status).toBe(status)
const html = await res.text()
const $ = cheerio.load(html)
const curParams = JSON.parse($('#params').text() || '{}')
expect(curParams).toEqual(params)
}
})
it('should handle partial-gen-params with page dynamicParams = false correctly', async () => {
for (const { path, status, params } of [
// these checks don't work with custom memory only
// cache handler
...(process.env.CUSTOM_CACHE_HANDLER
? []
: [
{
path: '/partial-gen-params-no-additional-slug/en/first',
status: 200,
params: { lang: 'en', slug: 'first' },
},
]),
{
path: '/partial-gen-params-no-additional-slug/de/first',
status: 404,
params: {},
},
{
path: '/partial-gen-params-no-additional-slug/en/non-existent',
status: 404,
params: {},
},
]) {
const res = await next.fetch(path)
expect(res.status).toBe(status)
const html = await res.text()
const $ = cheerio.load(html)
const curParams = JSON.parse($('#params').text() || '{}')
expect(curParams).toEqual(params)
}
})
// fetch cache in generateStaticParams needs fs for persistence
// so doesn't behave as expected with custom in memory only
// cache handler
if (!process.env.CUSTOM_CACHE_HANDLER) {
it('should honor fetch cache in generateStaticParams', async () => {
const initialRes = await next.fetch(
`/partial-gen-params-no-additional-lang/en/first`
)
expect(initialRes.status).toBe(200)
// we can't read prerender-manifest from deployment
if (isNextDeploy) return
let langFetchSlug
let slugFetchSlug
if (isDev) {
await check(() => {
const matches = stripAnsi(next.cliOutput).match(
/partial-gen-params fetch ([\d]{1,})/
)
if (matches[1]) {
langFetchSlug = matches[1]
slugFetchSlug = langFetchSlug
}
return langFetchSlug ? 'success' : next.cliOutput
}, 'success')
} else {
// the fetch cache can potentially be a miss since
// the generateStaticParams are executed parallel
// in separate workers so parse value from
// prerender-manifest
const routes = Object.keys(prerenderManifest.routes)
for (const route of routes) {
const langSlug = route.match(
/partial-gen-params-no-additional-lang\/en\/([\d]{1,})/
)?.[1]
if (langSlug) {
langFetchSlug = langSlug
}
const slugSlug = route.match(
/partial-gen-params-no-additional-slug\/en\/([\d]{1,})/
)?.[1]
if (slugSlug) {
slugFetchSlug = slugSlug
}
}
}
require('console').log({ langFetchSlug, slugFetchSlug })
for (const { pathname, slug } of [
{
pathname: '/partial-gen-params-no-additional-lang/en',
slug: langFetchSlug,
},
{
pathname: '/partial-gen-params-no-additional-slug/en',
slug: slugFetchSlug,
},
]) {
const res = await next.fetch(`${pathname}/${slug}`)
expect(res.status).toBe(200)
expect(
JSON.parse(
cheerio
.load(await res.text())('#params')
.text()
)
).toEqual({ lang: 'en', slug })
}
})
}
it('should honor fetch cache correctly', async () => {
await check(async () => {
const res = await fetchViaHTTP(
next.url,
'/variable-revalidate/revalidate-3'
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const layoutData = $('#layout-data').text()
const pageData = $('#page-data').text()
const res2 = await fetchViaHTTP(
next.url,
'/variable-revalidate/revalidate-3'
)
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#layout-data').text()).toBe(layoutData)
expect($2('#page-data').text()).toBe(pageData)
return 'success'
}, 'success')
})
it('should honor fetch cache correctly (edge)', async () => {
await check(async () => {
const res = await fetchViaHTTP(
next.url,
'/variable-revalidate-edge/revalidate-3'
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
// the test cache handler is simple and doesn't share
// state across workers so not guaranteed to have cache hit
if (!(isNextDeploy && process.env.CUSTOM_CACHE_HANDLER)) {
const layoutData = $('#layout-data').text()
const pageData = $('#page-data').text()
const res2 = await fetchViaHTTP(
next.url,
'/variable-revalidate-edge/revalidate-3'
)
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#layout-data').text()).toBe(layoutData)
expect($2('#page-data').text()).toBe(pageData)
}
return 'success'
}, 'success')
})
it('should cache correctly with authorization header and revalidate', async () => {
await check(async () => {
const res = await fetchViaHTTP(
next.url,
'/variable-revalidate/authorization'
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const layoutData = $('#layout-data').text()
const pageData = $('#page-data').text()
const res2 = await fetchViaHTTP(
next.url,
'/variable-revalidate/authorization'
)
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#layout-data').text()).toBe(layoutData)
expect($2('#page-data').text()).toBe(pageData)
return 'success'
}, 'success')
})
it('should not cache correctly with POST method request init', async () => {
const res = await fetchViaHTTP(
next.url,
'/variable-revalidate-edge/post-method-request'
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const pageData2 = $('#page-data2').text()
for (let i = 0; i < 3; i++) {
const res2 = await fetchViaHTTP(
next.url,
'/variable-revalidate-edge/post-method-request'
)
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#page-data2').text()).not.toBe(pageData2)
}
})
it('should cache correctly with post method and revalidate', async () => {
await check(async () => {
const res = await fetchViaHTTP(
next.url,
'/variable-revalidate/post-method'
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const layoutData = $('#layout-data').text()
const pageData = $('#page-data').text()
const dataBody1 = $('#data-body1').text()
const dataBody2 = $('#data-body2').text()
const dataBody3 = $('#data-body3').text()
const dataBody4 = $('#data-body4').text()
expect(dataBody1).not.toBe(dataBody2)
expect(dataBody2).not.toBe(dataBody3)
expect(dataBody3).not.toBe(dataBody4)
const res2 = await fetchViaHTTP(
next.url,
'/variable-revalidate/post-method'
)
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#layout-data').text()).toBe(layoutData)
expect($2('#page-data').text()).toBe(pageData)
expect($2('#data-body1').text()).toBe(dataBody1)
expect($2('#data-body2').text()).toBe(dataBody2)
expect($2('#data-body3').text()).toBe(dataBody3)
return 'success'
}, 'success')
})
it('should cache correctly with post method and revalidate edge', async () => {
await check(async () => {
const res = await fetchViaHTTP(
next.url,
'/variable-revalidate-edge/post-method'
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const layoutData = $('#layout-data').text()
const pageData = $('#page-data').text()
const dataBody1 = $('#data-body1').text()
const dataBody2 = $('#data-body2').text()
const dataBody3 = $('#data-body3').text()
const dataBody4 = $('#data-body4').text()
const res2 = await fetchViaHTTP(
next.url,
'/variable-revalidate-edge/post-method'
)
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#layout-data').text()).toBe(layoutData)
expect($2('#page-data').text()).toBe(pageData)
expect($2('#data-body1').text()).toBe(dataBody1)
expect($2('#data-body2').text()).toBe(dataBody2)
expect($2('#data-body3').text()).toBe(dataBody3)
expect($2('#data-body4').text()).toBe(dataBody4)
return 'success'
}, 'success')
})
it('should cache correctly with POST method and revalidate', async () => {
await check(async () => {
const res = await fetchViaHTTP(
next.url,
'/variable-revalidate/post-method'
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const layoutData = $('#layout-data').text()
const pageData = $('#page-data').text()
const res2 = await fetchViaHTTP(
next.url,
'/variable-revalidate/post-method'
)
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#layout-data').text()).toBe(layoutData)
expect($2('#page-data').text()).toBe(pageData)
return 'success'
}, 'success')
})
it('should cache correctly with cookie header and revalidate', async () => {
await check(async () => {
const res = await fetchViaHTTP(next.url, '/variable-revalidate/cookie')
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const layoutData = $('#layout-data').text()
const pageData = $('#page-data').text()
const res2 = await fetchViaHTTP(next.url, '/variable-revalidate/cookie')
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#layout-data').text()).toBe(layoutData)
expect($2('#page-data').text()).toBe(pageData)
return 'success'
}, 'success')
})
it('should cache correctly with utf8 encoding', async () => {
await check(async () => {
const res = await fetchViaHTTP(
next.url,
'/variable-revalidate/encoding'
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const layoutData = $('#layout-data').text()
const pageData = $('#page-data').text()
expect(JSON.parse(pageData).jp).toBe(
'超鬼畜激辛ボム兵スピンジャンプ Bomb Spin Jump'
)
const res2 = await fetchViaHTTP(
next.url,
'/variable-revalidate/encoding'
)
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#layout-data').text()).toBe(layoutData)
expect($2('#page-data').text()).toBe(pageData)
return 'success'
}, 'success')
})
it('should cache correctly with utf8 encoding edge', async () => {
await check(async () => {
const res = await fetchViaHTTP(
next.url,
'/variable-revalidate-edge/encoding'
)
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
const layoutData = $('#layout-data').text()
const pageData = $('#page-data').text()
expect(JSON.parse(pageData).jp).toBe(
'超鬼畜激辛ボム兵スピンジャンプ Bomb Spin Jump'
)
const res2 = await fetchViaHTTP(
next.url,
'/variable-revalidate-edge/encoding'
)
expect(res2.status).toBe(200)
const html2 = await res2.text()
const $2 = cheerio.load(html2)
expect($2('#layout-data').text()).toBe(layoutData)
expect($2('#page-data').text()).toBe(pageData)
return 'success'
}, 'success')
})
it('Should not throw Dynamic Server Usage error when using generateStaticParams with previewData', async () => {
const browserOnIndexPage = await next.browser('/ssg-preview')
const content = await browserOnIndexPage
.elementByCss('#preview-data')
.text()
expect(content).toContain('previewData')
})
it('should force SSR correctly for headers usage', async () => {
const res = await next.fetch('/force-static', {
headers: {
Cookie: 'myCookie=cookieValue',
another: 'header',
},
})
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
expect(JSON.parse($('#headers').text())).toIncludeAllMembers([
'cookie',
'another',
])
expect(JSON.parse($('#cookies').text())).toEqual([
{
name: 'myCookie',
value: 'cookieValue',
},
])
const firstTime = $('#now').text()
if (!(global as any).isNextDev) {
const res2 = await next.fetch('/force-static')
expect(res2.status).toBe(200)
const $2 = cheerio.load(await res2.text())
expect(firstTime).not.toBe($2('#now').text())
}
})
it('should not error with generateStaticParams and dynamic data', async () => {
const res = await next.fetch('/gen-params-dynamic/one')
const html = await res.text()
expect(res.status).toBe(200)
expect(html).toContain('gen-params-dynamic/[slug]')
expect(html).toContain('one')
const data = cheerio.load(html)('#data').text()
for (let i = 0; i < 5; i++) {
const res2 = await next.fetch('/gen-params-dynamic/one')
expect(res2.status).toBe(200)
expect(
cheerio
.load(await res2.text())('#data')
.text()
).not.toBe(data)
}
})
it('should not error with generateStaticParams and authed data on revalidate', async () => {
const res = await next.fetch('/gen-params-dynamic-revalidate/one')
const html = await res.text()
expect(res.status).toBe(200)
expect(html).toContain('gen-params-dynamic/[slug]')
expect(html).toContain('one')
const initData = cheerio.load(html)('#data').text()
await check(async () => {
const res2 = await next.fetch('/gen-params-dynamic-revalidate/one')
expect(res2.status).toBe(200)
const $ = cheerio.load(await res2.text())
expect($('#data').text()).toBeTruthy()
expect($('#data').text()).not.toBe(initData)
return 'success'
}, 'success')
})
it('should honor dynamic = "force-static" correctly', async () => {
const res = await next.fetch('/force-static/first')
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
expect(JSON.parse($('#params').text())).toEqual({ slug: 'first' })
expect(JSON.parse($('#headers').text())).toEqual([])
expect(JSON.parse($('#cookies').text())).toEqual([])
const firstTime = $('#now').text()
if (!(global as any).isNextDev) {
const res2 = await next.fetch('/force-static/first')
expect(res2.status).toBe(200)
const $2 = cheerio.load(await res2.text())
expect(firstTime).toBe($2('#now').text())
}
})
it('should honor dynamic = "force-static" correctly (lazy)', async () => {
const res = await next.fetch('/force-static/random')
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
expect(JSON.parse($('#params').text())).toEqual({ slug: 'random' })
expect(JSON.parse($('#headers').text())).toEqual([])
expect(JSON.parse($('#cookies').text())).toEqual([])
const firstTime = $('#now').text()
if (!(global as any).isNextDev) {
const res2 = await next.fetch('/force-static/random')
expect(res2.status).toBe(200)
const $2 = cheerio.load(await res2.text())
expect(firstTime).toBe($2('#now').text())
}
})
// since we aren't leveraging fs cache with custom handler
// then these will 404 as they are cache misses
if (!(isNextStart && process.env.CUSTOM_CACHE_HANDLER)) {
it('should handle dynamicParams: false correctly', async () => {
const validParams = ['tim', 'seb', 'styfle']
for (const param of validParams) {
const res = await next.fetch(`/blog/${param}`, {
redirect: 'manual',
})
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
expect(JSON.parse($('#params').text())).toEqual({
author: param,
})
expect($('#page').text()).toBe('/blog/[author]')
}
const invalidParams = ['timm', 'non-existent']
for (const param of invalidParams) {
const invalidRes = await next.fetch(`/blog/${param}`, {
redirect: 'manual',
})
expect(invalidRes.status).toBe(404)
expect(await invalidRes.text()).toContain('page could not be found')
}
})
}
it('should work with forced dynamic path', async () => {
for (const slug of ['first', 'second']) {
const res = await next.fetch(`/dynamic-no-gen-params-ssr/${slug}`, {
redirect: 'manual',
})
expect(res.status).toBe(200)
expect(await res.text()).toContain(`${slug}`)
}
})
it('should work with dynamic path no generateStaticParams', async () => {
for (const slug of ['first', 'second']) {
const res = await next.fetch(`/dynamic-no-gen-params/${slug}`, {
redirect: 'manual',
})
expect(res.status).toBe(200)
expect(await res.text()).toContain(`${slug}`)
}
})
it('should handle dynamicParams: true correctly', async () => {
const paramsToCheck = [
{
author: 'tim',
slug: 'first-post',
},
{
author: 'seb',
slug: 'second-post',
},
{
author: 'styfle',
slug: 'first-post',
},
{
author: 'new-author',
slug: 'first-post',
},
]
for (const params of paramsToCheck) {
const res = await next.fetch(`/blog/${params.author}/${params.slug}`, {
redirect: 'manual',
})
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
expect(JSON.parse($('#params').text())).toEqual(params)
expect($('#page').text()).toBe('/blog/[author]/[slug]')
}
})
// since we aren't leveraging fs cache with custom handler
// then these will 404 as they are cache misses
if (!(isNextStart && process.env.CUSTOM_CACHE_HANDLER)) {
it('should navigate to static path correctly', async () => {
const browser = await next.browser('/blog/tim')
await browser.eval('window.beforeNav = 1')
expect(
await browser.eval('document.documentElement.innerHTML')
).toContain('/blog/[author]')
await browser.elementByCss('#author-2').click()
await check(async () => {
const params = JSON.parse(
await browser.elementByCss('#params').text()
)
return params.author === 'seb' ? 'found' : params
}, 'found')
expect(await browser.eval('window.beforeNav')).toBe(1)
await browser.elementByCss('#author-1-post-1').click()
await check(async () => {
const params = JSON.parse(
await browser.elementByCss('#params').text()
)
return params.author === 'tim' && params.slug === 'first-post'
? 'found'
: params
}, 'found')
expect(await browser.eval('window.beforeNav')).toBe(1)
await browser.back()
await check(async () => {
const params = JSON.parse(
await browser.elementByCss('#params').text()
)
return params.author === 'seb' ? 'found' : params
}, 'found')
expect(await browser.eval('window.beforeNav')).toBe(1)
})
}
it('should ssr dynamically when detected automatically with fetch cache option', async () => {
const pathname = '/ssr-auto/cache-no-store'
const initialRes = await next.fetch(pathname, {
redirect: 'manual',
})
expect(initialRes.status).toBe(200)
const initialHtml = await initialRes.text()
const initial$ = cheerio.load(initialHtml)
expect(initial$('#page').text()).toBe(pathname)
const initialDate = initial$('#date').text()
expect(initialHtml).toContain('Example Domain')
const secondRes = await next.fetch(pathname, {
redirect: 'manual',
})
expect(secondRes.status).toBe(200)
const secondHtml = await secondRes.text()
const second$ = cheerio.load(secondHtml)
expect(second$('#page').text()).toBe(pathname)
const secondDate = second$('#date').text()
expect(secondHtml).toContain('Example Domain')
expect(secondDate).not.toBe(initialDate)
})
it('should render not found pages correctly and fallback to the default one', async () => {
const res = await next.fetch(`/blog/shu/hi`, {
redirect: 'manual',
})
expect(res.status).toBe(404)
const html = await res.text()
expect(html).toInclude('"noindex"')
expect(html).toInclude('This page could not be found.')
})
// TODO-APP: support fetch revalidate case for dynamic rendering
it.skip('should ssr dynamically when detected automatically with fetch revalidate option', async () => {
const pathname = '/ssr-auto/fetch-revalidate-zero'
const initialRes = await next.fetch(pathname, {
redirect: 'manual',
})
expect(initialRes.status).toBe(200)
const initialHtml = await initialRes.text()
const initial$ = cheerio.load(initialHtml)
expect(initial$('#page').text()).toBe(pathname)
const initialDate = initial$('#date').text()
expect(initialHtml).toContain('Example Domain')
const secondRes = await next.fetch(pathname, {
redirect: 'manual',
})
expect(secondRes.status).toBe(200)
const secondHtml = await secondRes.text()
const second$ = cheerio.load(secondHtml)
expect(second$('#page').text()).toBe(pathname)
const secondDate = second$('#date').text()
expect(secondHtml).toContain('Example Domain')
expect(secondDate).not.toBe(initialDate)
})
it('should ssr dynamically when forced via config', async () => {
const initialRes = await next.fetch('/ssr-forced', {
redirect: 'manual',
})
expect(initialRes.status).toBe(200)
const initialHtml = await initialRes.text()
const initial$ = cheerio.load(initialHtml)
expect(initial$('#page').text()).toBe('/ssr-forced')
const initialDate = initial$('#date').text()
const secondRes = await next.fetch('/ssr-forced', {
redirect: 'manual',
})
expect(secondRes.status).toBe(200)
const secondHtml = await secondRes.text()
const second$ = cheerio.load(secondHtml)
expect(second$('#page').text()).toBe('/ssr-forced')
const secondDate = second$('#date').text()
expect(secondDate).not.toBe(initialDate)
})
describe('useSearchParams', () => {
describe('client', () => {
it('should bailout to client rendering - without suspense boundary', async () => {
const browser = await next.browser(
'/hooks/use-search-params?first=value&second=other&third'
)
expect(await browser.elementByCss('#params-first').text()).toBe(
'value'
)
expect(await browser.elementByCss('#params-second').text()).toBe(
'other'
)
expect(await browser.elementByCss('#params-third').text()).toBe('')
expect(await browser.elementByCss('#params-not-real').text()).toBe(
'N/A'
)
})
it('should bailout to client rendering - with suspense boundary', async () => {
const browser = await next.browser(
'/hooks/use-search-params/with-suspense?first=value&second=other&third'
)
expect(await browser.elementByCss('#params-first').text()).toBe(
'value'
)
expect(await browser.elementByCss('#params-second').text()).toBe(
'other'
)
expect(await browser.elementByCss('#params-third').text()).toBe('')
expect(await browser.elementByCss('#params-not-real').text()).toBe(
'N/A'
)
})
it.skip('should have empty search params on force-static', async () => {
const browser = await next.browser(
'/hooks/use-search-params/force-static?first=value&second=other&third'
)
expect(await browser.elementByCss('#params-first').text()).toBe('N/A')
expect(await browser.elementByCss('#params-second').text()).toBe(
'N/A'
)
expect(await browser.elementByCss('#params-third').text()).toBe('N/A')
expect(await browser.elementByCss('#params-not-real').text()).toBe(
'N/A'
)
await browser.elementById('to-use-search-params').click()
await browser.waitForElementByCss('#hooks-use-search-params')
// Should not be empty after navigating to another page with useSearchParams
expect(await browser.elementByCss('#params-first').text()).toBe('1')
expect(await browser.elementByCss('#params-second').text()).toBe('2')
expect(await browser.elementByCss('#params-third').text()).toBe('3')
expect(await browser.elementByCss('#params-not-real').text()).toBe(
'N/A'
)
})
// TODO-APP: re-enable after investigating rewrite params
if (!(global as any).isNextDeploy) {
it('should have values from canonical url on rewrite', async () => {
const browser = await next.browser(
'/rewritten-use-search-params?first=a&second=b&third=c'
)
expect(await browser.elementByCss('#params-first').text()).toBe('a')
expect(await browser.elementByCss('#params-second').text()).toBe(
'b'
)
expect(await browser.elementByCss('#params-third').text()).toBe('c')
expect(await browser.elementByCss('#params-not-real').text()).toBe(
'N/A'
)
})
}
})
// Don't run these tests in dev mode since they won't be statically generated
if (!isDev) {
describe('server response', () => {
it('should bailout to client rendering - without suspense boundary', async () => {
const res = await next.fetch('/hooks/use-search-params')
const html = await res.text()
expect(html).toInclude('<html id="__next_error__">')
})
it('should bailout to client rendering - with suspense boundary', async () => {
const res = await next.fetch(
'/hooks/use-search-params/with-suspense'
)
const html = await res.text()
expect(html).toInclude('<p>search params suspense</p>')
})
it.skip('should have empty search params on force-static', async () => {
const res = await next.fetch(
'/hooks/use-search-params/force-static?first=value&second=other&third'
)
const html = await res.text()
// Shouild not bail out to client rendering
expect(html).not.toInclude('<p>search params suspense</p>')
// Use empty search params instead
const $ = cheerio.load(html)
expect($('#params-first').text()).toBe('N/A')
expect($('#params-second').text()).toBe('N/A')
expect($('#params-third').text()).toBe('N/A')
expect($('#params-not-real').text()).toBe('N/A')
})
})
}
})
// TODO: needs updating as usePathname should not bail
describe.skip('usePathname', () => {
if (isDev) {
it('should bail out to client rendering during SSG', async () => {
const res = await next.fetch('/hooks/use-pathname/slug')
const html = await res.text()
expect(html).toInclude('<html id="__next_error__">')
})
}
it('should have the correct values', async () => {
const browser = await next.browser('/hooks/use-pathname/slug')
expect(await browser.elementByCss('#pathname').text()).toBe(
'/hooks/use-pathname/slug'
)
})
it('should have values from canonical url on rewrite', async () => {
const browser = await next.browser('/rewritten-use-pathname')
expect(await browser.elementByCss('#pathname').text()).toBe(
'/rewritten-use-pathname'
)
})
})
if (!(global as any).isNextDeploy) {
it('should show a message to leave feedback for `appDir`', async () => {
expect(next.cliOutput).toContain(
`Thank you for testing \`appDir\` please leave your feedback at https://nextjs.link/app-feedback`
)
})
}
it('should keep querystring on static page', async () => {
const browser = await next.browser('/blog/tim?message=hello-world')
const checkUrl = async () =>
expect(await browser.url()).toBe(
next.url + '/blog/tim?message=hello-world'
)
checkUrl()
await waitFor(1000)
checkUrl()
})
if (process.env.CUSTOM_CACHE_HANDLER && !isNextDeploy) {
it('should have logs from cache-handler', () => {
expect(next.cliOutput).toContain('initialized custom cache-handler')
expect(next.cliOutput).toContain('cache-handler get')
expect(next.cliOutput).toContain('cache-handler set')
})
}
}
)