first commit
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

This commit is contained in:
Arian Tron
2026-03-10 19:37:31 +03:30
commit 61f56f997c
27684 changed files with 2784175 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
import Link from 'next/link'
export const dynamic = 'force-static'
export function generateStaticParams() {
return [{ slug: 'first' }, { slug: 'second' }]
}
export default async function Page(props) {
const params = await props.params
return (
<main>
<h1>{params.slug}</h1>
<ul>
<li>
<Link href="/another">Visit another page</Link>
</li>
</ul>
</main>
)
}

View File

@@ -0,0 +1,26 @@
import Link from 'next/link'
export default function Another() {
return (
<main>
<h1>Another</h1>
<ul>
<li>
<Link href="/">Visit the home page</Link>
</li>
<li>
<Link href="/another">another page</Link>
</li>
<li>
<Link href="/another/first">another first page</Link>
</li>
<li>
<Link href="/another/second">another second page</Link>
</li>
<li>
<Link href="/image-import">image import page</Link>
</li>
</ul>
</main>
)
}

View File

@@ -0,0 +1,5 @@
export const dynamic = 'force-static'
export async function GET() {
return Response.json({ answer: 42 })
}

View File

@@ -0,0 +1,5 @@
export const revalidate = false
export async function GET() {
return new Response('this is plain text')
}

View File

@@ -0,0 +1,9 @@
'use client'
export default function Page() {
return (
<main>
<h1>Use Client Example</h1>
</main>
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -0,0 +1,18 @@
import Link from 'next/link'
import img from './test.png'
export default function ImageImport() {
return (
<main>
<h1>Image Import</h1>
<ul>
<li>
<Link href="/">Visit the home page</Link>
</li>
<li>
<a href={img.src}>View the image</a>
</li>
</ul>
</main>
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,7 @@
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}

View File

@@ -0,0 +1,3 @@
export default function NotFound() {
return <h1>My custom not found page</h1>
}

View File

@@ -0,0 +1,26 @@
import Link from 'next/link'
export default function Home() {
return (
<main>
<h1>Home</h1>
<ul>
<li>
<Link href="/another">another no trailingslash</Link>
</li>
<li>
<Link href="/another/">another has trailingslash</Link>
</li>
<li>
<Link href="/another/first">another first page</Link>
</li>
<li>
<Link href="/another/second">another second page</Link>
</li>
<li>
<Link href="/image-import">image import page</Link>
</li>
</ul>
</main>
)
}

View File

@@ -0,0 +1,2 @@
User-agent: *
Allow: /

View File

@@ -0,0 +1,8 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
trailingSlash: true,
// distDir: '.next-custom',
}
module.exports = nextConfig

View File

@@ -0,0 +1,104 @@
import { runNextCommand } from 'next-test-utils'
import { join } from 'path'
import { expectedWhenTrailingSlashTrue, getFiles } from './utils'
import { FileRef, isNextStart, nextTestSetup, PatchedFileRef } from 'e2e-utils'
describe('app dir - with output export', () => {
if (isNextStart) {
describe('with exportPathMap configured', () => {
let { next } = nextTestSetup({
files: {
app: new FileRef(join(__dirname, '..', 'app')),
'next.config.js': new PatchedFileRef(
join(__dirname, '..', 'next.config.js'),
(content) =>
content.replace(
'trailingSlash: true,',
`trailingSlash: true,
exportPathMap: async function (map) {
return map
},`
)
),
},
skipStart: true,
})
it('should throw', async () => {
let { exitCode, cliOutput } = await next.build()
expect(exitCode).toBe(1)
expect(cliOutput).toContain(
'The "exportPathMap" configuration cannot be used with the "app" directory. Please use generateStaticParams() instead.'
)
})
})
describe('without next config', () => {
let { next } = nextTestSetup({
files: {
app: new FileRef(join(__dirname, '..', 'app')),
},
skipStart: true,
})
it('should error when running next export', async () => {
let { exitCode } = await next.build()
expect(exitCode).toBe(0)
expect(await getFiles(join(next.testDir, 'out'))).toEqual([])
let stdout = ''
let stderr = ''
let error = undefined
try {
await runNextCommand(['export'], {
cwd: next.testDir,
onStdout(msg) {
stdout += msg
},
onStderr(msg) {
stderr += msg
},
})
} catch (e) {
error = e
}
expect(error).toBeDefined()
expect(stderr).toContain(
`\`next export\` has been removed in favor of 'output: export' in next.config.js`
)
expect(stdout).not.toContain('Export successful. Files written to')
expect(await getFiles(join(next.testDir, 'out'))).toEqual([])
})
})
describe('with distDir configured', () => {
let { next } = nextTestSetup({
files: {
app: new FileRef(join(__dirname, '..', 'app')),
'next.config.js': new PatchedFileRef(
join(__dirname, '..', 'next.config.js'),
(content) =>
content.replace(
'trailingSlash: true,',
`trailingSlash: true,
distDir: 'output',`
)
),
},
skipStart: true,
})
it('should correctly emit exported assets to config.distDir', async () => {
let { exitCode } = await next.build()
expect(exitCode).toBe(0)
const actualFiles = await getFiles(join(next.testDir, 'output'))
expect(actualFiles).toEqual(
expect.arrayContaining(expectedWhenTrailingSlashTrue)
)
expect(actualFiles).toHaveLength(expectedWhenTrailingSlashTrue.length)
})
})
} else {
it('skipped in dev', () => {})
}
})

View File

@@ -0,0 +1,25 @@
import { join } from 'path'
import { FileRef, isNextDev, nextTestSetup, PatchedFileRef } from 'e2e-utils'
describe('app dir - with output export and custom distDir in dev', () => {
if (isNextDev) {
const { next } = nextTestSetup({
files: {
app: new FileRef(join(__dirname, '..', 'app')),
'next.config.js': new PatchedFileRef(
join(__dirname, '..', 'next.config.js'),
(content) => content.replace('// distDir', 'distDir')
),
},
})
it('should render properly in dev', async () => {
expect(next.distDir).toContain('.next-custom')
const res = await next.render('/')
expect(res).toContain('Home')
})
} else {
it('skipped in prod', () => {})
}
})

View File

@@ -0,0 +1,37 @@
import { isNextDev } from 'e2e-utils'
import { runTests } from './utils'
describe('app dir - with output export - dynamic missing gsp', () => {
describe('should error when dynamic route is missing generateStaticParams', () => {
runTests({
dynamicPage: 'undefined',
generateStaticParamsOpt: 'set noop',
expectedErrMsg: isNextDev
? 'Page "/another/[slug]/page" is missing exported function "generateStaticParams()", which is required with "output: export" config.'
: 'Page "/another/[slug]" is missing "generateStaticParams()" so it cannot be used with "output: export" config.',
})
})
describe('should error when client component has generateStaticParams', () => {
const expectedErrMsg = process.env.IS_TURBOPACK_TEST
? 'App pages cannot use both "use client" and export function "generateStaticParams()".'
: 'Page "/another/[slug]/page" cannot use both "use client" and export function "generateStaticParams()".'
runTests({
dynamicPage: 'undefined',
generateStaticParamsOpt: 'set client',
expectedErrMsg: expectedErrMsg,
})
})
if (isNextDev) {
describe('should error when dynamic route is set to true', () => {
runTests({
dynamicPage: 'undefined',
dynamicParams: 'true',
expectedErrMsg:
'"dynamicParams: true" cannot be used with "output: export". See more info here: https://nextjs.org/docs/app/building-your-application/deploying/static-exports',
})
})
}
})

View File

@@ -0,0 +1,23 @@
import { runTests } from './utils'
describe('app dir - with output export - dynamic api route', () => {
describe.each([
{
dynamicApiRoute: 'undefined',
expectedErrMsg:
'export const dynamic = "force-static"/export const revalidate not configured on route',
},
{ dynamicApiRoute: "'error'" },
{ dynamicApiRoute: "'force-static'" },
{
dynamicApiRoute: "'force-dynamic'",
expectedErrMsg:
'export const dynamic = "force-dynamic" on page "/api/json" cannot be used with "output: export".',
},
])(
'should work in prod with dynamicApiRoute $dynamicApiRoute',
({ dynamicApiRoute, expectedErrMsg }) => {
runTests({ dynamicApiRoute, expectedErrMsg })
}
)
})

View File

@@ -0,0 +1,19 @@
import { runTests } from './utils'
describe('app dir - with output export - dynamic api route', () => {
describe.each([
{ dynamicPage: 'undefined' },
{ dynamicPage: "'error'" },
{ dynamicPage: "'force-static'" },
{
dynamicPage: "'force-dynamic'",
expectedErrMsg:
'Page with `dynamic = "force-dynamic"` couldn\'t be exported. `output: "export"` requires all pages be renderable statically',
},
])(
'should work in prod with dynamicPage $dynamicPage',
({ dynamicPage, expectedErrMsg }) => {
runTests({ dynamicPage, expectedErrMsg })
}
)
})

View File

@@ -0,0 +1,51 @@
import { join } from 'path'
import { isNextStart, nextTestSetup } from 'e2e-utils'
import { retry } from 'next-test-utils'
describe('app dir - with output export (next start)', () => {
if (isNextStart) {
const { next } = nextTestSetup({
files: join(__dirname, '..'),
skipStart: true,
})
it('should error during next start with output export', async () => {
const { exitCode } = await next.build()
expect(exitCode).toBe(0)
try {
await next.start({ skipBuild: true })
} catch (e) {}
await retry(() => {
expect(next.cliOutput).toContain(
`"next start" does not work with "output: export" configuration. Use "npx serve@latest out" instead.`
)
})
})
it('should warn during next start with output standalone', async () => {
await next.patchFile(
'next.config.js',
(content) =>
content.replace(`output: 'export'`, `output: 'standalone'`),
async () => {
const { exitCode } = await next.build()
expect(exitCode).toBe(0)
try {
await next.start({ skipBuild: true })
} catch (e) {}
await retry(() => {
expect(next.cliOutput).toContain(
`"next start" does not work with "output: standalone" configuration. Use "node .next/standalone/server.js" instead.`
)
})
}
)
})
} else {
it('skipped in dev', () => {})
}
})

View File

@@ -0,0 +1,10 @@
import { runTests } from './utils'
describe('app dir - with output export - trailing slash', () => {
describe.each([{ trailingSlash: false }, { trailingSlash: true }])(
"should work in prod with trailingSlash '$trailingSlash'",
({ trailingSlash }) => {
runTests({ trailingSlash })
}
)
})

View File

@@ -0,0 +1,401 @@
/* eslint-env jest */
import { join } from 'path'
import { promisify } from 'util'
import fs from 'fs-extra'
import globOrig from 'glob'
import {
waitForRedbox,
getRedboxHeader,
getRedboxSource,
retry,
findPort,
startStaticServer,
stopApp,
fetchViaHTTP,
} from 'next-test-utils'
import { nextTestSetup } from 'e2e-utils'
import webdriver from 'next-webdriver'
const glob = promisify(globOrig)
export const expectedWhenTrailingSlashTrue = [
'404.html',
'404/index.html',
'__next.__PAGE__.txt',
'__next._full.txt',
'__next._head.txt',
'__next._index.txt',
'__next._tree.txt',
// Turbopack and plain next.js have different hash output for the file name
// Turbopack will output favicon in the _next/static/media folder
...(process.env.IS_TURBOPACK_TEST
? [expect.stringMatching(/_next\/static\/media\/favicon\.[0-9a-f]+\.ico/)]
: []),
expect.stringMatching(/_next\/static\/media\/test\.[0-9a-f]+\.png/),
expect.stringMatching(/_next\/static\/[A-Za-z0-9_-]+\/_buildManifest.js/),
...(process.env.IS_TURBOPACK_TEST
? [
expect.stringMatching(
/_next\/static\/[A-Za-z0-9_-]+\/_clientMiddlewareManifest.js/
),
]
: []),
expect.stringMatching(/_next\/static\/[A-Za-z0-9_-]+\/_ssgManifest.js/),
'_not-found/__next._full.txt',
'_not-found/__next._head.txt',
'_not-found/__next._index.txt',
'_not-found/__next._not-found.__PAGE__.txt',
'_not-found/__next._not-found.txt',
'_not-found/__next._tree.txt',
'_not-found/index.html',
'_not-found/index.txt',
'another/__next._full.txt',
'another/__next._head.txt',
'another/__next._index.txt',
'another/__next._tree.txt',
'another/__next.another.__PAGE__.txt',
'another/__next.another.txt',
'another/first/__next._full.txt',
'another/first/__next._head.txt',
'another/first/__next._index.txt',
'another/first/__next._tree.txt',
'another/first/__next.another.$d$slug.__PAGE__.txt',
'another/first/__next.another.$d$slug.txt',
'another/first/__next.another.txt',
'another/first/index.html',
'another/first/index.txt',
'another/index.html',
'another/index.txt',
'another/second/__next._full.txt',
'another/second/__next._head.txt',
'another/second/__next._index.txt',
'another/second/__next._tree.txt',
'another/second/__next.another.$d$slug.__PAGE__.txt',
'another/second/__next.another.$d$slug.txt',
'another/second/__next.another.txt',
'another/second/index.html',
'another/second/index.txt',
'api/json',
'api/txt',
'client/__next._full.txt',
'client/__next._head.txt',
'client/__next._index.txt',
'client/__next._tree.txt',
'client/__next.client.__PAGE__.txt',
'client/__next.client.txt',
'client/index.html',
'client/index.txt',
'favicon.ico',
'image-import/__next._full.txt',
'image-import/__next._head.txt',
'image-import/__next._index.txt',
'image-import/__next._tree.txt',
'image-import/__next.image-import.__PAGE__.txt',
'image-import/__next.image-import.txt',
'image-import/index.html',
'image-import/index.txt',
'index.html',
'index.txt',
'robots.txt',
]
const expectedWhenTrailingSlashFalse = [
'404.html',
'__next.__PAGE__.txt',
'__next._full.txt',
'__next._head.txt',
'__next._index.txt',
'__next._tree.txt',
// Turbopack will output favicon in the _next/static/media folder
...(process.env.IS_TURBOPACK_TEST
? [expect.stringMatching(/_next\/static\/media\/favicon\.[0-9a-f]+\.ico/)]
: []),
expect.stringMatching(/_next\/static\/media\/test\.[0-9a-f]+\.png/),
expect.stringMatching(/_next\/static\/[A-Za-z0-9_-]+\/_buildManifest.js/),
...(process.env.IS_TURBOPACK_TEST
? [
expect.stringMatching(
/_next\/static\/[A-Za-z0-9_-]+\/_clientMiddlewareManifest.js/
),
]
: []),
expect.stringMatching(/_next\/static\/[A-Za-z0-9_-]+\/_ssgManifest.js/),
'_not-found.html',
'_not-found.txt',
'_not-found/__next._full.txt',
'_not-found/__next._head.txt',
'_not-found/__next._index.txt',
'_not-found/__next._not-found.__PAGE__.txt',
'_not-found/__next._not-found.txt',
'_not-found/__next._tree.txt',
'another.html',
'another.txt',
'another/__next._full.txt',
'another/__next._head.txt',
'another/__next._index.txt',
'another/__next._tree.txt',
'another/__next.another.__PAGE__.txt',
'another/__next.another.txt',
'another/first.html',
'another/first.txt',
'another/first/__next._full.txt',
'another/first/__next._head.txt',
'another/first/__next._index.txt',
'another/first/__next._tree.txt',
'another/first/__next.another.$d$slug.__PAGE__.txt',
'another/first/__next.another.$d$slug.txt',
'another/first/__next.another.txt',
'another/second.html',
'another/second.txt',
'another/second/__next._full.txt',
'another/second/__next._head.txt',
'another/second/__next._index.txt',
'another/second/__next._tree.txt',
'another/second/__next.another.$d$slug.__PAGE__.txt',
'another/second/__next.another.$d$slug.txt',
'another/second/__next.another.txt',
'api/json',
'api/txt',
'client.html',
'client.txt',
'client/__next._full.txt',
'client/__next._head.txt',
'client/__next._index.txt',
'client/__next._tree.txt',
'client/__next.client.__PAGE__.txt',
'client/__next.client.txt',
'favicon.ico',
'image-import.html',
'image-import.txt',
'image-import/__next._full.txt',
'image-import/__next._head.txt',
'image-import/__next._index.txt',
'image-import/__next._tree.txt',
'image-import/__next.image-import.__PAGE__.txt',
'image-import/__next.image-import.txt',
'index.html',
'index.txt',
'robots.txt',
]
export async function getFiles(cwd) {
const opts = { cwd, nodir: true }
const files = ((await glob('**/*', opts)) as string[])
.filter(
(f) =>
!f.startsWith('_next/static/chunks/') &&
!f.startsWith('_next/static/development/') &&
!f.startsWith('_next/static/webpack/')
)
.sort()
return files
}
export function runTests({
trailingSlash = true,
dynamicPage,
dynamicParams,
dynamicApiRoute,
generateStaticParamsOpt,
expectedErrMsg,
}: {
trailingSlash?: boolean
dynamicPage?: string
dynamicParams?: string
dynamicApiRoute?: string
generateStaticParamsOpt?: 'set noop' | 'set client'
expectedErrMsg?: string | RegExp
}) {
let { next, skipped, isNextDev } = nextTestSetup({
files: join(__dirname, '..'),
skipDeployment: true,
skipStart: true,
disableAutoSkewProtection: true,
})
if (skipped) {
return
}
beforeAll(async () => {
if (trailingSlash !== undefined) {
await next.patchFile('next.config.js', (content) =>
content.replace(
'trailingSlash: true,',
`trailingSlash: ${trailingSlash},`
)
)
}
if (dynamicPage !== undefined) {
await next.patchFile('app/another/[slug]/page.js', (content) =>
content.replace(
`export const dynamic = 'force-static'`,
dynamicPage === 'undefined'
? ''
: `export const dynamic = ${dynamicPage}`
)
)
}
if (dynamicApiRoute !== undefined) {
await next.patchFile('app/api/json/route.js', (content) =>
content.replace(
`export const dynamic = 'force-static'`,
`export const dynamic = ${dynamicApiRoute}`
)
)
}
if (dynamicParams !== undefined) {
await next.patchFile(
'app/another/[slug]/page.js',
(content) => `export const dynamicParams = ${dynamicParams}\n` + content
)
}
if (generateStaticParamsOpt === 'set noop') {
await next.patchFile('app/another/[slug]/page.js', (content) =>
content.replace('export function generateStaticParams', 'function noop')
)
} else if (generateStaticParamsOpt === 'set client') {
await next.patchFile(
'app/another/[slug]/page.js',
(content) => '"use client"\n' + content
)
}
})
let port: number
let stopOrKill: (() => Promise<void>) | undefined
beforeAll(async () => {
if (isNextDev) {
await next.start()
port = Number(next.appPort)
} else {
await next.build()
port = await findPort()
const app = await startStaticServer(join(next.testDir, 'out'), null, port)
stopOrKill = () => stopApp(app)
}
})
afterAll(async () => {
if (stopOrKill) {
await stopOrKill()
}
})
it('should work', async () => {
if (expectedErrMsg) {
if (isNextDev) {
const url = dynamicPage ? '/another/first' : '/api/json'
const browser = await webdriver(port, url)
await waitForRedbox(browser)
const header = await getRedboxHeader(browser)
const source = await getRedboxSource(browser)
if (expectedErrMsg instanceof RegExp) {
expect(`${header}\n${source}`).toContain(expectedErrMsg)
} else {
expect(`${header}\n${source}`).toContain(expectedErrMsg)
}
} else {
await retry(() => expect(next.cliOutput).toMatch(/error/i))
}
expect(next.cliOutput).toMatch(expectedErrMsg)
} else {
const a = (n: number) => `li:nth-child(${n}) a`
const browser = await webdriver(port, '/')
await retry(async () =>
expect(await browser.elementByCss('h1').text()).toContain('Home')
)
expect(await browser.elementByCss(a(1)).text()).toBe(
'another no trailingslash'
)
await browser.elementByCss(a(1)).click()
await retry(async () =>
expect(await browser.elementByCss('h1').text()).toContain('Another')
)
expect(await browser.elementByCss(a(1)).text()).toBe(
'Visit the home page'
)
await browser.elementByCss(a(1)).click()
await retry(async () =>
expect(await browser.elementByCss('h1').text()).toContain('Home')
)
expect(await browser.elementByCss(a(2)).text()).toBe(
'another has trailingslash'
)
await browser.elementByCss(a(2)).click()
await retry(async () =>
expect(await browser.elementByCss('h1').text()).toContain('Another')
)
expect(await browser.elementByCss(a(1)).text()).toBe(
'Visit the home page'
)
await browser.elementByCss(a(1)).click()
await retry(async () =>
expect(await browser.elementByCss('h1').text()).toContain('Home')
)
expect(await browser.elementByCss(a(3)).text()).toBe('another first page')
await browser.elementByCss(a(3)).click()
await retry(async () =>
expect(await browser.elementByCss('h1').text()).toContain('first')
)
expect(await browser.elementByCss(a(1)).text()).toBe('Visit another page')
await browser.elementByCss(a(1)).click()
await retry(async () =>
expect(await browser.elementByCss('h1').text()).toContain('Another')
)
expect(await browser.elementByCss(a(4)).text()).toBe(
'another second page'
)
await browser.elementByCss(a(4)).click()
await retry(async () =>
expect(await browser.elementByCss('h1').text()).toContain('second')
)
expect(await browser.elementByCss(a(1)).text()).toBe('Visit another page')
await browser.elementByCss(a(1)).click()
await retry(async () =>
expect(await browser.elementByCss('h1').text()).toContain('Another')
)
expect(await browser.elementByCss(a(5)).text()).toBe('image import page')
await browser.elementByCss(a(5)).click()
await retry(async () =>
expect(await browser.elementByCss('h1').text()).toContain(
'Image Import'
)
)
expect(await browser.elementByCss(a(2)).text()).toBe('View the image')
expect(await browser.elementByCss(a(2)).getAttribute('href')).toMatch(
/\/test\.(.*)\.png/
)
const res1 = await fetchViaHTTP(port, '/api/json')
expect(res1.status).toBe(200)
expect(await res1.json()).toEqual({ answer: 42 })
const res2 = await fetchViaHTTP(port, '/api/txt')
expect(res2.status).toBe(200)
expect(await res2.text()).toEqual('this is plain text')
if (!isNextDev) {
let outputDir = join(next.testDir, 'out')
const expected = trailingSlash
? expectedWhenTrailingSlashTrue
: expectedWhenTrailingSlashFalse
const actualFiles = await getFiles(outputDir)
expect(actualFiles).toEqual(expect.arrayContaining(expected))
expect(actualFiles).toHaveLength(expected.length)
const html404 = await fs.readFile(join(outputDir, '404.html'), 'utf8')
expect(html404).toContain('<h1>My custom not found page</h1>')
}
}
})
}