Refactor more tests to createNextDescribe (#44104)

This commit is contained in:
Tim Neutkens 2022-12-19 10:20:01 +01:00 committed by GitHub
parent b8ae447b0f
commit 7a2ec41601
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 2752 additions and 3318 deletions

View file

@ -5,11 +5,6 @@ import { NextInstance } from 'test/lib/next-modes/base'
import path from 'path'
describe('ReactRefresh app', () => {
if (process.env.NEXT_TEST_REACT_VERSION === '^17') {
it('should skip for react v17', () => {})
return
}
let next: NextInstance
beforeAll(async () => {

View file

@ -5,11 +5,6 @@ import path from 'path'
// TODO-APP: Investigate snapshot mismatch
describe('ReactRefreshLogBox app', () => {
if (process.env.NEXT_TEST_REACT_VERSION === '^17') {
it('should skip for react v17', () => {})
return
}
let next: NextInstance
beforeAll(async () => {

View file

@ -7,11 +7,6 @@ import path from 'path'
// TODO: figure out why snapshots mismatch on GitHub actions
// specifically but work in docker and locally
describe.skip('ReactRefreshLogBox app', () => {
if (process.env.NEXT_TEST_REACT_VERSION === '^17') {
it('should skip for react v17', () => {})
return
}
let next: NextInstance
beforeAll(async () => {

View file

@ -6,11 +6,6 @@ import { check } from 'next-test-utils'
import path from 'path'
describe('ReactRefreshLogBox app', () => {
if (process.env.NEXT_TEST_REACT_VERSION === '^17') {
it('should skip for react v17', () => {})
return
}
let next: NextInstance
beforeAll(async () => {

View file

@ -6,11 +6,6 @@ import path from 'path'
// TODO: re-enable these tests after figuring out what is causing
// them to be so unreliable in CI
describe.skip('ReactRefreshLogBox app', () => {
if (process.env.NEXT_TEST_REACT_VERSION === '^17') {
it('should skip for react v17', () => {})
return
}
let next: NextInstance
beforeAll(async () => {

View file

@ -4,11 +4,6 @@ import { NextInstance } from 'test/lib/next-modes/base'
import { sandbox } from './helpers'
describe('ReactRefreshModule app', () => {
if (process.env.NEXT_TEST_REACT_VERSION === '^17') {
it('should skip for react v17', () => {})
return
}
let next: NextInstance
beforeAll(async () => {

View file

@ -5,11 +5,6 @@ import { NextInstance } from 'test/lib/next-modes/base'
import path from 'path'
describe('ReactRefreshRegression app', () => {
if (process.env.NEXT_TEST_REACT_VERSION === '^17') {
it('should skip for react v17', () => {})
return
}
let next: NextInstance
beforeAll(async () => {

View file

@ -6,11 +6,6 @@ import path from 'path'
import { check } from 'next-test-utils'
describe('Error Overlay for server components', () => {
if (process.env.NEXT_TEST_REACT_VERSION === '^17') {
it('should skip for react v17', () => {})
return
}
let next: NextInstance
beforeAll(async () => {

View file

@ -532,14 +532,10 @@ describe('basic HMR', () => {
)
)
const isReact17 = process.env.NEXT_TEST_REACT_VERSION === '^17'
expect(await hasRedbox(browser)).toBe(true)
// TODO: Replace this when webpack 5 is the default
expect(await getRedboxHeader(browser)).toMatch(
`Objects are not valid as a React child (found: ${
isReact17 ? '/search/' : '[object RegExp]'
}). If you meant to render a collection of children, use an array instead.`
`Objects are not valid as a React child (found: [object RegExp]). If you meant to render a collection of children, use an array instead.`
)
await next.patchFile(aboutPage, aboutContent)

View file

@ -597,14 +597,10 @@ describe('basic HMR', () => {
)
)
const isReact17 = process.env.NEXT_TEST_REACT_VERSION === '^17'
expect(await hasRedbox(browser)).toBe(true)
// TODO: Replace this when webpack 5 is the default
expect(await getRedboxHeader(browser)).toMatch(
`Objects are not valid as a React child (found: ${
isReact17 ? '/search/' : '[object RegExp]'
}). If you meant to render a collection of children, use an array instead.`
`Objects are not valid as a React child (found: [object RegExp]). If you meant to render a collection of children, use an array instead.`
)
await next.patchFile(aboutPage, aboutContent)

View file

@ -1,56 +1,45 @@
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { renderViaHTTP } from 'next-test-utils'
import webdriver from 'next-webdriver'
import { createNextDescribe } from 'e2e-utils'
import path from 'path'
import { readJSON } from 'fs-extra'
describe('app-dir alias handling', () => {
if ((global as any).isNextDeploy) {
it('should skip next deploy for now', () => {})
return
}
let next: NextInstance
beforeAll(async () => {
next = await createNext({
files: new FileRef(path.join(__dirname, 'app-alias')),
dependencies: {
react: 'latest',
'react-dom': 'latest',
typescript: 'latest',
'@types/react': 'latest',
'@types/node': 'latest',
},
packageJson: {
type: 'module',
},
createNextDescribe(
'app-dir alias handling',
{
files: path.join(__dirname, 'app-alias'),
dependencies: {
react: 'latest',
'react-dom': 'latest',
typescript: 'latest',
'@types/react': 'latest',
'@types/node': 'latest',
},
packageJson: {
type: 'module',
},
skipDeployment: true,
},
({ next, isNextDev }) => {
it('should handle typescript paths alias correctly', async () => {
const html = await next.render('/button')
expect(html).toContain('click</button>')
})
})
afterAll(() => next.destroy())
it('should handle typescript paths alias correctly', async () => {
const html = await renderViaHTTP(next.url, '/button')
expect(html).toContain('click</button>')
})
it('should resolve css imports from outside with src folder presented', async () => {
const browser = await webdriver(next.url, '/button')
const fontSize = await browser
.elementByCss('button')
.getComputedCss('font-size')
expect(fontSize).toBe('50px')
})
if (!(global as any).isNextDev) {
it('should generate app-build-manifest correctly', async () => {
// Remove other page CSS files:
const manifest = await readJSON(
path.join(next.testDir, '.next', 'app-build-manifest.json')
)
expect(manifest.pages).not.toBeEmptyObject()
it('should resolve css imports from outside with src folder presented', async () => {
const browser = await next.browser('/button')
const fontSize = await browser
.elementByCss('button')
.getComputedCss('font-size')
expect(fontSize).toBe('50px')
})
if (!isNextDev) {
it('should generate app-build-manifest correctly', async () => {
// Remove other page CSS files:
const manifest = await next.readJSON(
path.join('.next', 'app-build-manifest.json')
)
expect(manifest.pages).not.toBeEmptyObject()
})
}
}
})
)

View file

@ -1,32 +1,23 @@
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { renderViaHTTP } from 'next-test-utils'
import { createNextDescribe } from 'e2e-utils'
import path from 'path'
describe('app-dir global edge configuration', () => {
if ((global as any).isNextDeploy) {
it('should skip next deploy for now', () => {})
return
}
let next: NextInstance
beforeAll(async () => {
next = await createNext({
files: new FileRef(path.join(__dirname, 'app-edge-global')),
dependencies: {
react: 'latest',
'react-dom': 'latest',
typescript: 'latest',
'@types/react': 'latest',
'@types/node': 'latest',
},
createNextDescribe(
'app-dir global edge configuration',
{
files: path.join(__dirname, 'app-edge-global'),
dependencies: {
react: 'latest',
'react-dom': 'latest',
typescript: 'latest',
'@types/react': 'latest',
'@types/node': 'latest',
},
skipDeployment: true,
},
({ next }) => {
it('should handle edge only routes', async () => {
const html = await next.render('/app-edge')
expect(html).toContain('<p>Edge!</p>')
})
})
afterAll(() => next.destroy())
it('should handle edge only routes', async () => {
const appHtml = await renderViaHTTP(next.url, '/app-edge')
expect(appHtml).toContain('<p>Edge!</p>')
})
})
}
)

View file

@ -1,80 +1,72 @@
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { check, renderViaHTTP } from 'next-test-utils'
import { createNextDescribe } from 'e2e-utils'
import { check } from 'next-test-utils'
import path from 'path'
describe('app-dir edge SSR', () => {
if ((global as any).isNextDeploy) {
it('should skip next deploy for now', () => {})
return
}
createNextDescribe(
'app-dir edge SSR',
{
files: path.join(__dirname, 'app-edge'),
dependencies: {
react: 'latest',
'react-dom': 'latest',
typescript: 'latest',
'@types/react': 'latest',
'@types/node': 'latest',
},
skipDeployment: true,
},
({ next }) => {
it('should handle edge only routes', async () => {
const appHtml = await next.render('/app-edge')
expect(appHtml).toContain('<p>Edge!</p>')
let next: NextInstance
beforeAll(async () => {
next = await createNext({
files: new FileRef(path.join(__dirname, 'app-edge')),
dependencies: {
react: 'latest',
'react-dom': 'latest',
typescript: 'latest',
'@types/react': 'latest',
'@types/node': 'latest',
},
const pageHtml = await next.render('/pages-edge')
expect(pageHtml).toContain('<p>pages-edge-ssr</p>')
})
})
afterAll(() => next.destroy())
it('should handle edge only routes', async () => {
const appHtml = await renderViaHTTP(next.url, '/app-edge')
expect(appHtml).toContain('<p>Edge!</p>')
const pageHtml = await renderViaHTTP(next.url, '/pages-edge')
expect(pageHtml).toContain('<p>pages-edge-ssr</p>')
})
if ((globalThis as any).isNextDev) {
it('should resolve module without error in edge runtime', async () => {
const logs = []
next.on('stderr', (log) => {
logs.push(log)
if ((globalThis as any).isNextDev) {
it('should resolve module without error in edge runtime', async () => {
const logs = []
next.on('stderr', (log) => {
logs.push(log)
})
await next.render('app-edge')
expect(
logs.some((log) => log.includes(`Attempted import error:`))
).toBe(false)
})
await renderViaHTTP(next.url, 'app-edge')
expect(logs.some((log) => log.includes(`Attempted import error:`))).toBe(
false
)
})
it('should handle edge rsc hmr', async () => {
const pageFile = 'app/app-edge/page.tsx'
const content = await next.readFile(pageFile)
it('should handle edge rsc hmr', async () => {
const pageFile = 'app/app-edge/page.tsx'
const content = await next.readFile(pageFile)
// Update rendered content
const updatedContent = content.replace('Edge!', 'edge-hmr')
await next.patchFile(pageFile, updatedContent)
await check(async () => {
const html = await renderViaHTTP(next.url, '/app-edge')
return html
}, /edge-hmr/)
// Update rendered content
const updatedContent = content.replace('Edge!', 'edge-hmr')
await next.patchFile(pageFile, updatedContent)
await check(async () => {
const html = await next.render('/app-edge')
return html
}, /edge-hmr/)
// Revert
await next.patchFile(pageFile, content)
await check(async () => {
const html = await renderViaHTTP(next.url, '/app-edge')
return html
}, /Edge!/)
})
} else {
// Production tests
it('should generate matchers correctly in middleware manifest', async () => {
const manifest = JSON.parse(
await next.readFile('.next/server/middleware-manifest.json')
)
expect(manifest.functions['/(group)/group/page'].matchers).toEqual([
{
regexp: '^/group$',
},
])
})
// Revert
await next.patchFile(pageFile, content)
await check(async () => {
const html = await next.render('/app-edge')
return html
}, /Edge!/)
})
} else {
// Production tests
it('should generate matchers correctly in middleware manifest', async () => {
const manifest = JSON.parse(
await next.readFile('.next/server/middleware-manifest.json')
)
expect(manifest.functions['/(group)/group/page'].matchers).toEqual([
{
regexp: '^/group$',
},
])
})
}
}
})
)

View file

@ -1,8 +1,5 @@
import path from 'path'
import { renderViaHTTP, fetchViaHTTP } from 'next-test-utils'
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import webdriver from 'next-webdriver'
import { createNextDescribe } from 'e2e-utils'
async function resolveStreamResponse(response: any, onData?: any) {
let result = ''
@ -18,183 +15,169 @@ async function resolveStreamResponse(response: any, onData?: any) {
return result
}
describe('app dir - external dependency', () => {
let next: NextInstance
if ((global as any).isNextDeploy) {
it('should skip for deploy mode for now', () => {})
return
}
beforeAll(async () => {
next = await createNext({
files: new FileRef(path.join(__dirname, './app-external')),
dependencies: {
'@next/font': 'canary',
react: 'latest',
'react-dom': 'latest',
swr: '2.0.0-rc.0',
createNextDescribe(
'app dir - external dependency',
{
files: path.join(__dirname, './app-external'),
dependencies: {
'@next/font': 'canary',
react: 'latest',
'react-dom': 'latest',
swr: '2.0.0-rc.0',
},
packageJson: {
scripts: {
setup: `cp -r ./node_modules_bak/* ./node_modules`,
build: 'yarn setup && next build',
dev: 'yarn setup && next dev',
start: 'next start',
},
packageJson: {
scripts: {
setup: `cp -r ./node_modules_bak/* ./node_modules`,
build: 'yarn setup && next build',
dev: 'yarn setup && next dev',
start: 'next start',
},
},
installCommand: 'yarn',
startCommand: (global as any).isNextDev ? 'yarn dev' : 'yarn start',
buildCommand: 'yarn build',
})
})
afterAll(() => next.destroy())
const { isNextDeploy } = global as any
const isReact17 = process.env.NEXT_TEST_REACT_VERSION === '^17'
if (isNextDeploy || isReact17) {
it('should skip tests for next-deploy and react 17', () => {})
return
}
it('should be able to opt-out 3rd party packages being bundled in server components', async () => {
await fetchViaHTTP(next.url, '/react-server/optout').then(
async (response) => {
},
installCommand: 'yarn',
startCommand: (global as any).isNextDev ? 'yarn dev' : 'yarn start',
buildCommand: 'yarn build',
skipDeployment: true,
},
({ next }) => {
it('should be able to opt-out 3rd party packages being bundled in server components', async () => {
await next.fetch('/react-server/optout').then(async (response) => {
const result = await resolveStreamResponse(response)
expect(result).toContain('Server: index.default')
expect(result).toContain('Server subpath: subpath.default')
expect(result).toContain('Client: index.default')
expect(result).toContain('Client subpath: subpath.default')
}
)
})
it('should handle external async module libraries correctly', async () => {
const clientHtml = await renderViaHTTP(next.url, '/external-imports/client')
const serverHtml = await renderViaHTTP(next.url, '/external-imports/server')
const sharedHtml = await renderViaHTTP(next.url, '/shared-esm-dep')
const browser = await webdriver(next.url, '/external-imports/client')
const browserClientText = await browser.elementByCss('#content').text()
function containClientContent(content) {
expect(content).toContain('module type:esm-export')
expect(content).toContain('export named:named')
expect(content).toContain('export value:123')
expect(content).toContain('export array:4,5,6')
expect(content).toContain('export object:{x:1}')
expect(content).toContain('swr-state')
}
containClientContent(clientHtml)
containClientContent(browserClientText)
// support esm module imports on server side, and indirect imports from shared components
expect(serverHtml).toContain('pure-esm-module')
expect(sharedHtml).toContain(
'node_modules instance from client module pure-esm-module'
)
})
it('should transpile specific external packages with the `transpilePackages` option', async () => {
const clientHtml = await renderViaHTTP(next.url, '/external-imports/client')
expect(clientHtml).toContain('transpilePackages:5')
})
it('should resolve the subset react in server components based on the react-server condition', async () => {
await fetchViaHTTP(next.url, '/react-server').then(async (response) => {
const result = await resolveStreamResponse(response)
expect(result).toContain('Server: <!-- -->subset')
expect(result).toContain('Client: <!-- -->full')
})
})
})
it('should resolve 3rd party package exports based on the react-server condition', async () => {
await fetchViaHTTP(next.url, '/react-server/3rd-party-package').then(
async (response) => {
it('should handle external async module libraries correctly', async () => {
const clientHtml = await next.render('/external-imports/client')
const serverHtml = await next.render('/external-imports/server')
const sharedHtml = await next.render('/shared-esm-dep')
const browser = await next.browser('/external-imports/client')
const browserClientText = await browser.elementByCss('#content').text()
function containClientContent(content) {
expect(content).toContain('module type:esm-export')
expect(content).toContain('export named:named')
expect(content).toContain('export value:123')
expect(content).toContain('export array:4,5,6')
expect(content).toContain('export object:{x:1}')
expect(content).toContain('swr-state')
}
containClientContent(clientHtml)
containClientContent(browserClientText)
// support esm module imports on server side, and indirect imports from shared components
expect(serverHtml).toContain('pure-esm-module')
expect(sharedHtml).toContain(
'node_modules instance from client module pure-esm-module'
)
})
it('should transpile specific external packages with the `transpilePackages` option', async () => {
const clientHtml = await next.render('/external-imports/client')
expect(clientHtml).toContain('transpilePackages:5')
})
it('should resolve the subset react in server components based on the react-server condition', async () => {
await next.fetch('/react-server').then(async (response) => {
const result = await resolveStreamResponse(response)
expect(result).toContain('Server: <!-- -->subset')
expect(result).toContain('Client: <!-- -->full')
})
})
it('should resolve 3rd party package exports based on the react-server condition', async () => {
await next
.fetch('/react-server/3rd-party-package')
.then(async (response) => {
const result = await resolveStreamResponse(response)
// Package should be resolved based on the react-server condition,
// as well as package's internal & external dependencies.
expect(result).toContain(
'Server: index.react-server:react.subset:dep.server'
)
expect(result).toContain(
'Client: index.default:react.full:dep.default'
)
// Subpath exports should be resolved based on the condition too.
expect(result).toContain('Server subpath: subpath.react-server')
expect(result).toContain('Client subpath: subpath.default')
})
})
it('should correctly collect global css imports and mark them as side effects', async () => {
await next.fetch('/css/a').then(async (response) => {
const result = await resolveStreamResponse(response)
// Package should be resolved based on the react-server condition,
// as well as package's internal & external dependencies.
expect(result).toContain(
'Server: index.react-server:react.subset:dep.server'
// It should include the global CSS import
expect(result).toMatch(/\.css/)
})
})
it('should handle external css modules', async () => {
const browser = await next.browser('/css/modules')
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('h1')).color`
)
expect(result).toContain('Client: index.default:react.full:dep.default')
// Subpath exports should be resolved based on the condition too.
expect(result).toContain('Server subpath: subpath.react-server')
expect(result).toContain('Client subpath: subpath.default')
}
)
})
it('should correctly collect global css imports and mark them as side effects', async () => {
await fetchViaHTTP(next.url, '/css/a').then(async (response) => {
const result = await resolveStreamResponse(response)
// It should include the global CSS import
expect(result).toMatch(/\.css/)
})
})
it('should handle external css modules', async () => {
const browser = await webdriver(next.url, '/css/modules')
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('h1')).color`
)
).toBe('rgb(255, 0, 0)')
})
it('should use the same export type for packages in both ssr and client', async () => {
const browser = await webdriver(next.url, '/client-dep')
expect(await browser.eval(`window.document.body.innerText`)).toBe('hello')
})
it('should handle external css modules in pages', async () => {
const browser = await webdriver(next.url, '/test-pages')
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('h1')).color`
)
).toBe('rgb(255, 0, 0)')
})
it('should handle external @next/font', async () => {
const browser = await webdriver(next.url, '/font')
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('p')).fontFamily`
)
).toMatch(/^__myFont_.{6}, __myFont_Fallback_.{6}$/)
})
describe('react in external esm packages', () => {
it('should use the same react in client app', async () => {
const html = await renderViaHTTP(next.url, '/esm/client')
const v1 = html.match(/App React Version: ([^<]+)</)[1]
const v2 = html.match(/External React Version: ([^<]+)</)[1]
expect(v1).toBe(v2)
).toBe('rgb(255, 0, 0)')
})
it('should use the same react in server app', async () => {
const html = await renderViaHTTP(next.url, '/esm/server')
const v1 = html.match(/App React Version: ([^<]+)</)[1]
const v2 = html.match(/External React Version: ([^<]+)</)[1]
expect(v1).toBe(v2)
it('should use the same export type for packages in both ssr and client', async () => {
const browser = await next.browser('/client-dep')
expect(await browser.eval(`window.document.body.innerText`)).toBe('hello')
})
it('should use the same react in pages', async () => {
const html = await renderViaHTTP(next.url, '/test-pages-esm')
it('should handle external css modules in pages', async () => {
const browser = await next.browser('/test-pages')
const v1 = html.match(/App React Version: ([^<]+)</)[1]
const v2 = html.match(/External React Version: ([^<]+)</)[1]
expect(v1).toBe(v2)
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('h1')).color`
)
).toBe('rgb(255, 0, 0)')
})
})
})
it('should handle external @next/font', async () => {
const browser = await next.browser('/font')
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('p')).fontFamily`
)
).toMatch(/^__myFont_.{6}, __myFont_Fallback_.{6}$/)
})
describe('react in external esm packages', () => {
it('should use the same react in client app', async () => {
const html = await next.render('/esm/client')
const v1 = html.match(/App React Version: ([^<]+)</)[1]
const v2 = html.match(/External React Version: ([^<]+)</)[1]
expect(v1).toBe(v2)
})
it('should use the same react in server app', async () => {
const html = await next.render('/esm/server')
const v1 = html.match(/App React Version: ([^<]+)</)[1]
const v2 = html.match(/External React Version: ([^<]+)</)[1]
expect(v1).toBe(v2)
})
it('should use the same react in pages', async () => {
const html = await next.render('/test-pages-esm')
const v1 = html.match(/App React Version: ([^<]+)</)[1]
const v2 = html.match(/External React Version: ([^<]+)</)[1]
expect(v1).toBe(v2)
})
})
}
)

View file

@ -1,158 +1,150 @@
/* eslint-env jest */
import { NextInstance } from 'test/lib/next-modes/base'
import { fetchViaHTTP, renderViaHTTP } from 'next-test-utils'
import { createNext, FileRef } from 'e2e-utils'
import { createNextDescribe, FileRef } from 'e2e-utils'
import cheerio from 'cheerio'
import path from 'path'
describe('app-dir with middleware', () => {
if ((global as any).isNextDeploy) {
it('should skip next deploy for now', () => {})
return
}
let next: NextInstance
afterAll(() => next.destroy())
beforeAll(async () => {
next = await createNext({
files: new FileRef(path.join(__dirname, 'app-middleware')),
})
})
describe.each([
{
title: 'Serverless Functions',
path: '/api/dump-headers-serverless',
toJson: (res: Response) => res.json(),
},
{
title: 'Edge Functions',
path: '/api/dump-headers-edge',
toJson: (res: Response) => res.json(),
},
{
title: 'next/headers',
path: '/headers',
toJson: async (res: Response) => {
const $ = cheerio.load(await res.text())
return JSON.parse($('#headers').text())
createNextDescribe(
'app-dir with middleware',
{
files: path.join(__dirname, 'app-middleware'),
skipDeployment: true,
},
({ next }) => {
describe.each([
{
title: 'Serverless Functions',
path: '/api/dump-headers-serverless',
toJson: (res: Response) => res.json(),
},
},
])('Mutate request headers for $title', ({ path, toJson }) => {
it(`Adds new headers`, async () => {
const res = await fetchViaHTTP(next.url, path, null, {
headers: {
{
title: 'Edge Functions',
path: '/api/dump-headers-edge',
toJson: (res: Response) => res.json(),
},
{
title: 'next/headers',
path: '/headers',
toJson: async (res: Response) => {
const $ = cheerio.load(await res.text())
return JSON.parse($('#headers').text())
},
},
])('Mutate request headers for $title', ({ path, toJson }) => {
it(`Adds new headers`, async () => {
const res = await next.fetch(path, null, {
headers: {
'x-from-client': 'hello-from-client',
},
})
expect(await toJson(res)).toMatchObject({
'x-from-client': 'hello-from-client',
},
'x-from-middleware': 'hello-from-middleware',
})
})
expect(await toJson(res)).toMatchObject({
'x-from-client': 'hello-from-client',
'x-from-middleware': 'hello-from-middleware',
})
})
it(`Deletes headers`, async () => {
const res = await fetchViaHTTP(
next.url,
path,
{
'remove-headers': 'x-from-client1,x-from-client2',
},
{
headers: {
'x-from-client1': 'hello-from-client',
'X-From-Client2': 'hello-from-client',
it(`Deletes headers`, async () => {
const res = await next.fetch(
path,
{
'remove-headers': 'x-from-client1,x-from-client2',
},
}
)
{
headers: {
'x-from-client1': 'hello-from-client',
'X-From-Client2': 'hello-from-client',
},
}
)
const json = await toJson(res)
expect(json).not.toHaveProperty('x-from-client1')
expect(json).not.toHaveProperty('X-From-Client2')
expect(json).toMatchObject({
'x-from-middleware': 'hello-from-middleware',
const json = await toJson(res)
expect(json).not.toHaveProperty('x-from-client1')
expect(json).not.toHaveProperty('X-From-Client2')
expect(json).toMatchObject({
'x-from-middleware': 'hello-from-middleware',
})
// Should not be included in response headers.
expect(res.headers.get('x-middleware-override-headers')).toBeNull()
expect(
res.headers.get('x-middleware-request-x-from-middleware')
).toBeNull()
expect(
res.headers.get('x-middleware-request-x-from-client1')
).toBeNull()
expect(
res.headers.get('x-middleware-request-x-from-client2')
).toBeNull()
})
// Should not be included in response headers.
expect(res.headers.get('x-middleware-override-headers')).toBeNull()
expect(
res.headers.get('x-middleware-request-x-from-middleware')
).toBeNull()
expect(res.headers.get('x-middleware-request-x-from-client1')).toBeNull()
expect(res.headers.get('x-middleware-request-x-from-client2')).toBeNull()
})
it(`Updates headers`, async () => {
const res = await fetchViaHTTP(
next.url,
path,
{
'update-headers':
'x-from-client1=new-value1,x-from-client2=new-value2',
},
{
headers: {
'x-from-client1': 'old-value1',
'X-From-Client2': 'old-value2',
'x-from-client3': 'old-value3',
it(`Updates headers`, async () => {
const res = await next.fetch(
path,
{
'update-headers':
'x-from-client1=new-value1,x-from-client2=new-value2',
},
}
)
expect(await toJson(res)).toMatchObject({
'x-from-client1': 'new-value1',
'x-from-client2': 'new-value2',
'x-from-client3': 'old-value3',
'x-from-middleware': 'hello-from-middleware',
{
headers: {
'x-from-client1': 'old-value1',
'X-From-Client2': 'old-value2',
'x-from-client3': 'old-value3',
},
}
)
expect(await toJson(res)).toMatchObject({
'x-from-client1': 'new-value1',
'x-from-client2': 'new-value2',
'x-from-client3': 'old-value3',
'x-from-middleware': 'hello-from-middleware',
})
// Should not be included in response headers.
expect(res.headers.get('x-middleware-override-headers')).toBeNull()
expect(
res.headers.get('x-middleware-request-x-from-middleware')
).toBeNull()
expect(
res.headers.get('x-middleware-request-x-from-client1')
).toBeNull()
expect(
res.headers.get('x-middleware-request-x-from-client2')
).toBeNull()
expect(
res.headers.get('x-middleware-request-x-from-client3')
).toBeNull()
})
// Should not be included in response headers.
expect(res.headers.get('x-middleware-override-headers')).toBeNull()
expect(
res.headers.get('x-middleware-request-x-from-middleware')
).toBeNull()
expect(res.headers.get('x-middleware-request-x-from-client1')).toBeNull()
expect(res.headers.get('x-middleware-request-x-from-client2')).toBeNull()
expect(res.headers.get('x-middleware-request-x-from-client3')).toBeNull()
})
})
})
describe('app dir middleware without pages dir', () => {
if ((global as any).isNextDeploy) {
it('should skip next deploy for now', () => {})
return
}
)
let next: NextInstance
createNextDescribe(
'app dir middleware without pages dir',
{
files: {
app: new FileRef(path.join(__dirname, 'app-middleware/app')),
'next.config.js': new FileRef(
path.join(__dirname, 'app-middleware/next.config.js')
),
'middleware.js': `
import { NextResponse } from 'next/server'
afterAll(() => next.destroy())
beforeAll(async () => {
next = await createNext({
files: {
app: new FileRef(path.join(__dirname, 'app-middleware/app')),
'next.config.js': new FileRef(
path.join(__dirname, 'app-middleware/next.config.js')
),
'middleware.js': `
import { NextResponse } from 'next/server'
export async function middleware(request) {
return new NextResponse('redirected')
}
export async function middleware(request) {
return new NextResponse('redirected')
}
export const config = {
matcher: '/headers'
}
`,
},
skipDeployment: true,
},
({ next }) => {
// eslint-disable-next-line jest/no-identical-title
it('Updates headers', async () => {
const html = await next.render('/headers')
export const config = {
matcher: '/headers'
}
`,
},
expect(html).toContain('redirected')
})
})
it(`Updates headers`, async () => {
const html = await renderViaHTTP(next.url, '/headers')
expect(html).toContain('redirected')
})
})
}
)

File diff suppressed because it is too large Load diff

View file

@ -1,62 +1,42 @@
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { fetchViaHTTP, renderViaHTTP } from 'next-test-utils'
import { createNextDescribe } from 'e2e-utils'
import path from 'path'
import cheerio from 'cheerio'
import webdriver from 'next-webdriver'
describe('app-dir assetPrefix handling', () => {
if ((global as any).isNextDeploy) {
it('should skip next deploy for now', () => {})
return
}
let next: NextInstance
beforeAll(async () => {
next = await createNext({
files: new FileRef(path.join(__dirname, 'asset-prefix')),
dependencies: {
react: 'latest',
'react-dom': 'latest',
},
skipStart: true,
createNextDescribe(
'app-dir assetPrefix handling',
{
files: path.join(__dirname, 'asset-prefix'),
skipDeployment: true,
},
({ next }) => {
it('should redirect route when requesting it directly', async () => {
const res = await next.fetch(
'/a/',
{},
{
redirect: 'manual',
}
)
expect(res.status).toBe(308)
expect(res.headers.get('location')).toBe(next.url + '/a')
})
await next.start()
})
afterAll(() => next.destroy())
it('should render link', async () => {
const $ = await next.render$('/')
expect($('#to-a-trailing-slash').attr('href')).toBe('/a')
})
it('should redirect route when requesting it directly', async () => {
const res = await fetchViaHTTP(
next.url,
'/a/',
{},
{
redirect: 'manual',
}
)
expect(res.status).toBe(308)
expect(res.headers.get('location')).toBe(next.url + '/a')
})
it('should redirect route when requesting it directly by browser', async () => {
const browser = await next.browser('/a')
expect(await browser.waitForElementByCss('#a-page').text()).toBe('A page')
})
it('should render link', async () => {
const html = await renderViaHTTP(next.url, '/')
const $ = cheerio.load(html)
expect($('#to-a-trailing-slash').attr('href')).toBe('/a')
})
it('should redirect route when requesting it directly by browser', async () => {
const browser = await webdriver(next.url, '/a')
expect(await browser.waitForElementByCss('#a-page').text()).toBe('A page')
})
it('should redirect route when clicking link', async () => {
const browser = await webdriver(next.url, '/')
await browser
.elementByCss('#to-a-trailing-slash')
.click()
.waitForElementByCss('#a-page')
expect(await browser.waitForElementByCss('#a-page').text()).toBe('A page')
})
})
it('should redirect route when clicking link', async () => {
const browser = await next.browser('/')
await browser
.elementByCss('#to-a-trailing-slash')
.click()
.waitForElementByCss('#a-page')
expect(await browser.waitForElementByCss('#a-page').text()).toBe('A page')
})
}
)

View file

@ -1,29 +1,18 @@
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import webdriver from 'next-webdriver'
import { createNextDescribe } from 'e2e-utils'
import path from 'path'
describe('async-component-preload', () => {
if ((global as any).isNextDeploy) {
it('should skip next deploy for now', () => {})
return
}
let next: NextInstance
beforeAll(async () => {
next = await createNext({
files: new FileRef(path.join(__dirname, 'async-component-preload')),
dependencies: {
react: 'latest',
'react-dom': 'latest',
},
createNextDescribe(
'async-component-preload',
{
files: path.join(__dirname, 'async-component-preload'),
skipDeployment: true,
},
({ next }) => {
it('should handle redirect in an async page', async () => {
const browser = await next.browser('/')
expect(await browser.waitForElementByCss('#success').text()).toBe(
'Success'
)
})
})
afterAll(() => next.destroy())
it('should handle redirect in an async page', async () => {
const browser = await webdriver(next.url, '/')
expect(await browser.waitForElementByCss('#success').text()).toBe('Success')
})
})
}
)

View file

@ -1,42 +1,30 @@
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { createNextDescribe } from 'e2e-utils'
import path from 'path'
import webdriver from 'next-webdriver'
// TODO-APP: fix test as it's failing randomly
describe.skip('app-dir back button download bug', () => {
if ((global as any).isNextDeploy) {
it('should skip next deploy for now', () => {})
return
}
createNextDescribe(
'app-dir back button download bug',
{
files: path.join(__dirname, 'back-button-download-bug'),
skipDeployment: true,
},
({ next }) => {
it('should redirect route when clicking link', async () => {
const browser = await next.browser('/')
const text = await browser
.elementByCss('#to-post-1')
.click()
.waitForElementByCss('#post-page')
.text()
expect(text).toBe('This is the post page')
let next: NextInstance
await browser.back()
beforeAll(async () => {
next = await createNext({
files: new FileRef(path.join(__dirname, 'back-button-download-bug')),
dependencies: {
react: 'latest',
'react-dom': 'latest',
},
skipStart: true,
})
await next.start()
})
afterAll(() => next.destroy())
it('should redirect route when clicking link', async () => {
const browser = await webdriver(next.url, '/')
const text = await browser
.elementByCss('#to-post-1')
.click()
.waitForElementByCss('#post-page')
.text()
expect(text).toBe('This is the post page')
await browser.back()
expect(await browser.waitForElementByCss('#home-page').text()).toBe('Home!')
})
expect(await browser.waitForElementByCss('#home-page').text()).toBe(
'Home!'
)
})
}
)
})

View file

@ -1,7 +1,6 @@
import path from 'path'
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import webdriver from 'next-webdriver'
import { check } from 'next-test-utils'
describe('app-dir create root layout', () => {
@ -12,10 +11,6 @@ describe('app-dir create root layout', () => {
return
}
if (process.env.NEXT_TEST_REACT_VERSION === '^17') {
it('should skip for react v17', () => {})
return
}
let next: NextInstance
if (isDev) {
@ -39,7 +34,7 @@ describe('app-dir create root layout', () => {
it('create root layout', async () => {
const outputIndex = next.cliOutput.length
const browser = await webdriver(next.url, '/route')
const browser = await next.browser('/route')
expect(await browser.elementById('page-text').text()).toBe(
'Hello world!'
@ -101,7 +96,7 @@ describe('app-dir create root layout', () => {
it('create root layout', async () => {
const outputIndex = next.cliOutput.length
const browser = await webdriver(next.url, '/')
const browser = await next.browser('/')
expect(await browser.elementById('page-text').text()).toBe(
'Hello world'
@ -168,7 +163,7 @@ describe('app-dir create root layout', () => {
it('create root layout', async () => {
const outputIndex = next.cliOutput.length
const browser = await webdriver(next.url, '/route/second/inner')
const browser = await next.browser('/route/second/inner')
expect(await browser.elementById('page-text').text()).toBe(
'Hello world'
@ -236,7 +231,7 @@ describe('app-dir create root layout', () => {
it('create root layout', async () => {
const outputIndex = next.cliOutput.length
const browser = await webdriver(next.url, '/')
const browser = await next.browser('/')
expect(await browser.elementById('page-text').text()).toBe(
'Hello world!'

View file

@ -1,75 +1,63 @@
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { createNextDescribe } from 'e2e-utils'
import { getRedboxDescription, hasRedbox } from 'next-test-utils'
import path from 'path'
import webdriver from 'next-webdriver'
describe('dynamic-href', () => {
const isDev = (global as any).isNextDev
if ((global as any).isNextDeploy) {
it('should skip next deploy for now', () => {})
return
}
createNextDescribe(
'dynamic-href',
{
files: path.join(__dirname, 'dynamic-href'),
skipDeployment: true,
},
({ isNextDev: isDev, next }) => {
if (isDev) {
it('should error when using dynamic href.pathname in app dir', async () => {
const browser = await next.browser('/object')
let next: NextInstance
beforeAll(async () => {
next = await createNext({
files: new FileRef(path.join(__dirname, 'dynamic-href')),
dependencies: {
react: 'latest',
'react-dom': 'latest',
},
})
})
afterAll(() => next.destroy())
if (isDev) {
it('should error when using dynamic href.pathname in app dir', async () => {
const browser = await webdriver(next.url, '/object')
// Error should show up
expect(await hasRedbox(browser, true)).toBeTrue()
expect(await getRedboxDescription(browser)).toMatchInlineSnapshot(
`"Error: Dynamic href \`/object/[slug]\` found in <Link> while using the \`/app\` router, this is not supported. Read more: https://nextjs.org/docs/messages/app-dir-dynamic-href"`
)
// Fix error
const pageContent = await next.readFile('app/object/page.js')
await next.patchFile(
'app/object/page.js',
pageContent.replace(
"pathname: '/object/[slug]'",
"pathname: '/object/slug'"
// Error should show up
expect(await hasRedbox(browser, true)).toBeTrue()
expect(await getRedboxDescription(browser)).toMatchInlineSnapshot(
`"Error: Dynamic href \`/object/[slug]\` found in <Link> while using the \`/app\` router, this is not supported. Read more: https://nextjs.org/docs/messages/app-dir-dynamic-href"`
)
)
expect(await browser.waitForElementByCss('#link').text()).toBe('to slug')
// Navigate to new page
await browser.elementByCss('#link').click()
expect(await browser.waitForElementByCss('#pathname').text()).toBe(
'/object/slug'
)
expect(await browser.elementByCss('#slug').text()).toBe('1')
})
// Fix error
const pageContent = await next.readFile('app/object/page.js')
await next.patchFile(
'app/object/page.js',
pageContent.replace(
"pathname: '/object/[slug]'",
"pathname: '/object/slug'"
)
)
expect(await browser.waitForElementByCss('#link').text()).toBe(
'to slug'
)
it('should error when using dynamic href in app dir', async () => {
const browser = await webdriver(next.url, '/string')
// Navigate to new page
await browser.elementByCss('#link').click()
expect(await browser.waitForElementByCss('#pathname').text()).toBe(
'/object/slug'
)
expect(await browser.elementByCss('#slug').text()).toBe('1')
})
// Error should show up
expect(await hasRedbox(browser, true)).toBeTrue()
expect(await getRedboxDescription(browser)).toMatchInlineSnapshot(
`"Error: Dynamic href \`/object/[slug]\` found in <Link> while using the \`/app\` router, this is not supported. Read more: https://nextjs.org/docs/messages/app-dir-dynamic-href"`
)
})
} else {
it('should not error on /object in prod', async () => {
const browser = await webdriver(next.url, '/object')
expect(await browser.elementByCss('#link').text()).toBe('to slug')
})
it('should not error on /string in prod', async () => {
const browser = await webdriver(next.url, '/string')
expect(await browser.elementByCss('#link').text()).toBe('to slug')
})
it('should error when using dynamic href in app dir', async () => {
const browser = await next.browser('/string')
// Error should show up
expect(await hasRedbox(browser, true)).toBeTrue()
expect(await getRedboxDescription(browser)).toMatchInlineSnapshot(
`"Error: Dynamic href \`/object/[slug]\` found in <Link> while using the \`/app\` router, this is not supported. Read more: https://nextjs.org/docs/messages/app-dir-dynamic-href"`
)
})
} else {
it('should not error on /object in prod', async () => {
const browser = await next.browser('/object')
expect(await browser.elementByCss('#link').text()).toBe('to slug')
})
it('should not error on /string in prod', async () => {
const browser = await next.browser('/string')
expect(await browser.elementByCss('#link').text()).toBe('to slug')
})
}
}
})
)

View file

@ -1,35 +1,29 @@
import path from 'path'
import { getRedboxHeader, hasRedbox } from 'next-test-utils'
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import webdriver from 'next-webdriver'
import { createNextDescribe } from 'e2e-utils'
describe('app dir - global error', () => {
let next: NextInstance
const isDev = (global as any).isNextDev
beforeAll(async () => {
next = await createNext({
files: new FileRef(path.join(__dirname, './global-error')),
})
})
afterAll(() => next.destroy())
it('should trigger error component when an error happens during rendering', async () => {
const browser = await webdriver(next.url, '/throw')
await browser
.waitForElementByCss('#error-trigger-button')
.elementByCss('#error-trigger-button')
.click()
if (isDev) {
expect(await hasRedbox(browser)).toBe(true)
expect(await getRedboxHeader(browser)).toMatch(/Error: Client error/)
} else {
createNextDescribe(
'app dir - global error',
{
files: path.join(__dirname, './global-error'),
},
({ next, isNextDev }) => {
it('should trigger error component when an error happens during rendering', async () => {
const browser = await next.browser('/throw')
await browser
expect(await browser.elementByCss('#error').text()).toBe(
'Error message: Client error'
)
}
})
})
.waitForElementByCss('#error-trigger-button')
.elementByCss('#error-trigger-button')
.click()
if (isNextDev) {
expect(await hasRedbox(browser)).toBe(true)
expect(await getRedboxHeader(browser)).toMatch(/Error: Client error/)
} else {
await browser
expect(await browser.elementByCss('#error').text()).toBe(
'Error message: Client error'
)
}
})
}
)

View file

@ -1,38 +1,24 @@
import path from 'path'
import cheerio from 'cheerio'
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { renderViaHTTP } from 'next-test-utils'
import { createNextDescribe } from 'e2e-utils'
describe('app dir imports', () => {
if (process.env.NEXT_TEST_REACT_VERSION === '^17') {
it('should skip for react v17', () => {})
return
}
let next: NextInstance
function runTests() {
beforeAll(async () => {
next = await createNext({
files: new FileRef(path.join(__dirname, 'import')),
dependencies: {
react: 'latest',
'react-dom': 'latest',
typescript: 'latest',
'@types/react': 'latest',
'@types/node': 'latest',
},
})
})
afterAll(() => next.destroy())
createNextDescribe(
'app dir imports',
{
files: path.join(__dirname, 'import'),
dependencies: {
react: 'latest',
'react-dom': 'latest',
typescript: 'latest',
'@types/react': 'latest',
'@types/node': 'latest',
},
},
({ next }) => {
;['js', 'jsx', 'ts', 'tsx'].forEach((ext) => {
it(`we can import all components from .${ext}`, async () => {
const html = await renderViaHTTP(next.url, `/${ext}`)
const $ = cheerio.load(html)
const $ = await next.render$(`/${ext}`)
expect($('#js').text()).toBe('CompJs')
})
})
}
runTests()
})
)

File diff suppressed because it is too large Load diff

View file

@ -1,99 +1,69 @@
import path from 'path'
import { renderViaHTTP } from 'next-test-utils'
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import cheerio from 'cheerio'
import { createNextDescribe } from 'e2e-utils'
describe('app dir - layout params', () => {
let next: NextInstance
createNextDescribe(
'app dir - layout params',
{
files: path.join(__dirname, './layout-params'),
dependencies: {
react: 'latest',
'react-dom': 'latest',
typescript: 'latest',
'@types/react': 'latest',
'@types/node': 'latest',
},
},
({ next }) => {
describe('basic params', () => {
it('check layout without params get no params', async () => {
const $ = await next.render$('/base/something/another')
const ids = ['#root-layout', '#lvl1-layout']
ids.forEach((divId) => {
const params = $(`${divId} > div`)
expect(params.length).toBe(0)
})
})
beforeAll(async () => {
next = await createNext({
files: new FileRef(path.join(__dirname, './layout-params')),
dependencies: {
react: 'latest',
'react-dom': 'latest',
typescript: 'latest',
'@types/react': 'latest',
'@types/node': 'latest',
},
})
})
afterAll(() => next.destroy())
it("check layout renders just it's params", async () => {
const $ = await next.render$('/base/something/another')
const isReact17 = process.env.NEXT_TEST_REACT_VERSION === '^17'
expect($('#lvl2-layout > div').length).toBe(1)
expect($('#lvl2-param1').text()).toBe('"something"')
})
if (isReact17) {
it('should skip tests for react 17', () => {})
return
}
it('check topmost layout renders all params', async () => {
const $ = await next.render$('/base/something/another')
describe('basic params', () => {
it('check layout without params get no params', async () => {
const html = await renderViaHTTP(next.url, '/base/something/another')
const $ = cheerio.load(html)
const ids = ['#root-layout', '#lvl1-layout']
ids.forEach((divId) => {
const params = $(`${divId} > div`)
expect(params.length).toBe(0)
expect($('#lvl3-layout > div').length).toBe(2)
expect($('#lvl3-param1').text()).toBe('"something"')
expect($('#lvl3-param2').text()).toBe('"another"')
})
})
it("check layout renders just it's params", async () => {
const html = await renderViaHTTP(next.url, '/base/something/another')
describe('catchall params', () => {
it('should give catchall params just to last layout', async () => {
const $ = await next.render$('/catchall/something/another')
const $ = cheerio.load(html)
expect($(`#root-layout > div`).length).toBe(0)
expect($('#lvl2-layout > div').length).toBe(1)
expect($('#lvl2-params').text()).toBe('["something","another"]')
})
expect($('#lvl2-layout > div').length).toBe(1)
expect($('#lvl2-param1').text()).toBe('"something"')
it('should give optional catchall params just to last layout', async () => {
const $ = await next.render$('/optional-catchall/something/another')
expect($(`#root-layout > div`).length).toBe(0)
expect($('#lvl2-layout > div').length).toBe(1)
expect($('#lvl2-params').text()).toBe('["something","another"]')
})
it("should give empty optional catchall params won't give params to any layout", async () => {
const $ = await next.render$('/optional-catchall')
expect($(`#root-layout > div`).length).toBe(0)
expect($('#lvl2-layout > div').length).toBe(0)
})
})
it('check topmost layout renders all params', async () => {
const html = await renderViaHTTP(next.url, '/base/something/another')
const $ = cheerio.load(html)
expect($('#lvl3-layout > div').length).toBe(2)
expect($('#lvl3-param1').text()).toBe('"something"')
expect($('#lvl3-param2').text()).toBe('"another"')
})
})
describe('catchall params', () => {
it('should give catchall params just to last layout', async () => {
const html = await renderViaHTTP(next.url, '/catchall/something/another')
const $ = cheerio.load(html)
expect($(`#root-layout > div`).length).toBe(0)
expect($('#lvl2-layout > div').length).toBe(1)
expect($('#lvl2-params').text()).toBe('["something","another"]')
})
it('should give optional catchall params just to last layout', async () => {
const html = await renderViaHTTP(
next.url,
'/optional-catchall/something/another'
)
const $ = cheerio.load(html)
expect($(`#root-layout > div`).length).toBe(0)
expect($('#lvl2-layout > div').length).toBe(1)
expect($('#lvl2-params').text()).toBe('["something","another"]')
})
it("should give empty optional catchall params won't give params to any layout", async () => {
const html = await renderViaHTTP(next.url, '/optional-catchall')
const $ = cheerio.load(html)
expect($(`#root-layout > div`).length).toBe(0)
expect($('#lvl2-layout > div').length).toBe(0)
})
})
})
}
)

View file

@ -1,373 +1,354 @@
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { getRedboxSource, hasRedbox, renderViaHTTP } from 'next-test-utils'
import cheerio from 'cheerio'
import { createNextDescribe } from 'e2e-utils'
import { getRedboxSource, hasRedbox } from 'next-test-utils'
import path from 'path'
import webdriver from 'next-webdriver'
describe('app dir next-font', () => {
const isDev = (global as any).isNextDev
if ((global as any).isNextDeploy) {
it('should skip next deploy for now', () => {})
return
createNextDescribe(
'app dir next-font',
{
files: path.join(__dirname, 'next-font'),
dependencies: {
'@next/font': 'canary',
react: 'latest',
'react-dom': 'latest',
},
skipDeployment: true,
},
({ next, isNextDev: isDev }) => {
describe('import values', () => {
it('should have correct values at /', async () => {
const $ = await next.render$('/')
// layout
expect(JSON.parse($('#root-layout').text())).toEqual({
className: expect.stringMatching(/^__className_.{6}$/),
variable: expect.stringMatching(/^__variable_.{6}$/),
style: {
fontFamily: expect.stringMatching(/^'__font1_.{6}'$/),
},
})
// page
expect(JSON.parse($('#root-page').text())).toEqual({
className: expect.stringMatching(/^__className_.{6}$/),
variable: expect.stringMatching(/^__variable_.{6}$/),
style: {
fontFamily: expect.stringMatching(/^'__font2_.{6}'$/),
},
})
// Comp
expect(JSON.parse($('#root-comp').text())).toEqual({
className: expect.stringMatching(/^__className_.{6}$/),
style: {
fontFamily: expect.stringMatching(/^'__font3_.{6}'$/),
fontStyle: 'italic',
fontWeight: 900,
},
})
})
it('should have correct values at /client', async () => {
const $ = await next.render$('/client')
// root layout
expect(JSON.parse($('#root-layout').text())).toEqual({
className: expect.stringMatching(/^__className_.{6}$/),
variable: expect.stringMatching(/^__variable_.{6}$/),
style: {
fontFamily: expect.stringMatching(/^'__font1_.{6}'$/),
},
})
// layout
expect(JSON.parse($('#client-layout').text())).toEqual({
className: expect.stringMatching(/^__className_.{6}$/),
style: {
fontFamily: expect.stringMatching(/^'__font4_.{6}'$/),
fontWeight: 100,
},
})
// page
expect(JSON.parse($('#client-page').text())).toEqual({
className: expect.stringMatching(/^__className_.{6}$/),
style: {
fontFamily: expect.stringMatching(/^'__font5_.{6}'$/),
fontStyle: 'italic',
},
})
// Comp
expect(JSON.parse($('#client-comp').text())).toEqual({
className: expect.stringMatching(/^__className_.{6}$/),
style: {
fontFamily: expect.stringMatching(/^'__font6_.{6}'$/),
},
})
})
})
describe('computed styles', () => {
it('should have correct styles at /', async () => {
const browser = await next.browser('/')
// layout
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-layout")).fontFamily'
)
).toMatch(/^__font1_.{6}$/)
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-layout")).fontWeight'
)
).toBe('400')
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-layout")).fontStyle'
)
).toBe('normal')
// page
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-page")).fontFamily'
)
).toMatch(/^__font2_.{6}$/)
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-page")).fontWeight'
)
).toBe('400')
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-page")).fontStyle'
)
).toBe('normal')
// Comp
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-comp")).fontFamily'
)
).toMatch(/^__font3_.{6}$/)
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-comp")).fontWeight'
)
).toBe('900')
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-comp")).fontStyle'
)
).toBe('italic')
})
it('should have correct styles at /client', async () => {
const browser = await next.browser('/client')
// root layout
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-layout")).fontFamily'
)
).toMatch(/^__font1_.{6}$/)
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-layout")).fontWeight'
)
).toBe('400')
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-layout")).fontStyle'
)
).toBe('normal')
// layout
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#client-layout")).fontFamily'
)
).toMatch(/^__font4_.{6}$/)
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#client-layout")).fontWeight'
)
).toBe('100')
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#client-layout")).fontStyle'
)
).toBe('normal')
// page
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#client-page")).fontFamily'
)
).toMatch(/^__font5_.{6}$/)
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#client-page")).fontWeight'
)
).toBe('400')
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#client-page")).fontStyle'
)
).toBe('italic')
// Comp
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#client-comp")).fontFamily'
)
).toMatch(/^__font6_.{6}$/)
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#client-comp")).fontWeight'
)
).toBe('400')
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#client-comp")).fontStyle'
)
).toBe('normal')
})
})
if (!isDev) {
describe('preload', () => {
it('should preload correctly with server components', async () => {
const $ = await next.render$('/')
// Preconnect
expect($('link[rel="preconnect"]').length).toBe(0)
expect($('link[as="font"]').length).toBe(3)
expect($('link[as="font"]').get(0).attribs).toEqual({
as: 'font',
crossorigin: '',
href: '/_next/static/media/e9b9dc0d8ba35f48.p.woff2',
rel: 'preload',
type: 'font/woff2',
})
expect($('link[as="font"]').get(1).attribs).toEqual({
as: 'font',
crossorigin: '',
href: '/_next/static/media/b61859a50be14c53.p.woff2',
rel: 'preload',
type: 'font/woff2',
})
expect($('link[as="font"]').get(2).attribs).toEqual({
as: 'font',
crossorigin: '',
href: '/_next/static/media/b2104791981359ae.p.woff2',
rel: 'preload',
type: 'font/woff2',
})
})
it('should preload correctly with client components', async () => {
const $ = await next.render$('/client')
// Preconnect
expect($('link[rel="preconnect"]').length).toBe(0)
expect($('link[as="font"]').length).toBe(3)
// From root layout
expect($('link[as="font"]').get(0).attribs).toEqual({
as: 'font',
crossorigin: '',
href: '/_next/static/media/e9b9dc0d8ba35f48.p.woff2',
rel: 'preload',
type: 'font/woff2',
})
expect($('link[as="font"]').get(1).attribs).toEqual({
as: 'font',
crossorigin: '',
href: '/_next/static/media/e1053f04babc7571.p.woff2',
rel: 'preload',
type: 'font/woff2',
})
expect($('link[as="font"]').get(2).attribs).toEqual({
as: 'font',
crossorigin: '',
href: '/_next/static/media/feab2c68f2a8e9a4.p.woff2',
rel: 'preload',
type: 'font/woff2',
})
})
it('should preload correctly with layout using fonts', async () => {
const $ = await next.render$('/layout-with-fonts')
// Preconnect
expect($('link[rel="preconnect"]').length).toBe(0)
expect($('link[as="font"]').length).toBe(2)
// From root layout
expect($('link[as="font"]').get(0).attribs).toEqual({
as: 'font',
crossorigin: '',
href: '/_next/static/media/e9b9dc0d8ba35f48.p.woff2',
rel: 'preload',
type: 'font/woff2',
})
expect($('link[as="font"]').get(1).attribs).toEqual({
as: 'font',
crossorigin: '',
href: '/_next/static/media/75c5faeeb9c86969.p.woff2',
rel: 'preload',
type: 'font/woff2',
})
})
it('should preload correctly with page using fonts', async () => {
const $ = await next.render$('/page-with-fonts')
// Preconnect
expect($('link[rel="preconnect"]').length).toBe(0)
expect($('link[as="font"]').length).toBe(2)
// From root layout
expect($('link[as="font"]').get(0).attribs).toEqual({
as: 'font',
crossorigin: '',
href: '/_next/static/media/e9b9dc0d8ba35f48.p.woff2',
rel: 'preload',
type: 'font/woff2',
})
expect($('link[as="font"]').get(1).attribs).toEqual({
as: 'font',
crossorigin: '',
href: '/_next/static/media/568e4c6d8123c4d6.p.woff2',
rel: 'preload',
type: 'font/woff2',
})
})
})
}
if (isDev) {
describe('Dev errors', () => {
it('should recover on font loader error', async () => {
const browser = await next.browser('/')
const font1Content = await next.readFile('fonts/index.js')
// Break file
await next.patchFile(
'fonts/index.js',
font1Content.replace('./font1.woff2', './does-not-exist.woff2')
)
expect(await hasRedbox(browser, true)).toBeTrue()
expect(await getRedboxSource(browser)).toInclude(
"Can't resolve './does-not-exist.woff2'"
)
// Fix file
await next.patchFile('fonts/index.js', font1Content)
await browser.waitForElementByCss('#root-page')
})
})
}
}
let next: NextInstance
beforeAll(async () => {
next = await createNext({
files: new FileRef(path.join(__dirname, 'next-font')),
dependencies: {
'@next/font': 'canary',
react: 'latest',
'react-dom': 'latest',
},
skipStart: true,
})
await next.start()
})
afterAll(() => next.destroy())
describe('import values', () => {
it('should have correct values at /', async () => {
const html = await renderViaHTTP(next.url, '/')
const $ = cheerio.load(html)
// layout
expect(JSON.parse($('#root-layout').text())).toEqual({
className: expect.stringMatching(/^__className_.{6}$/),
variable: expect.stringMatching(/^__variable_.{6}$/),
style: {
fontFamily: expect.stringMatching(/^'__font1_.{6}'$/),
},
})
// page
expect(JSON.parse($('#root-page').text())).toEqual({
className: expect.stringMatching(/^__className_.{6}$/),
variable: expect.stringMatching(/^__variable_.{6}$/),
style: {
fontFamily: expect.stringMatching(/^'__font2_.{6}'$/),
},
})
// Comp
expect(JSON.parse($('#root-comp').text())).toEqual({
className: expect.stringMatching(/^__className_.{6}$/),
style: {
fontFamily: expect.stringMatching(/^'__font3_.{6}'$/),
fontStyle: 'italic',
fontWeight: 900,
},
})
})
it('should have correct values at /client', async () => {
const html = await renderViaHTTP(next.url, '/client')
const $ = cheerio.load(html)
// root layout
expect(JSON.parse($('#root-layout').text())).toEqual({
className: expect.stringMatching(/^__className_.{6}$/),
variable: expect.stringMatching(/^__variable_.{6}$/),
style: {
fontFamily: expect.stringMatching(/^'__font1_.{6}'$/),
},
})
// layout
expect(JSON.parse($('#client-layout').text())).toEqual({
className: expect.stringMatching(/^__className_.{6}$/),
style: {
fontFamily: expect.stringMatching(/^'__font4_.{6}'$/),
fontWeight: 100,
},
})
// page
expect(JSON.parse($('#client-page').text())).toEqual({
className: expect.stringMatching(/^__className_.{6}$/),
style: {
fontFamily: expect.stringMatching(/^'__font5_.{6}'$/),
fontStyle: 'italic',
},
})
// Comp
expect(JSON.parse($('#client-comp').text())).toEqual({
className: expect.stringMatching(/^__className_.{6}$/),
style: {
fontFamily: expect.stringMatching(/^'__font6_.{6}'$/),
},
})
})
})
describe('computed styles', () => {
it('should have correct styles at /', async () => {
const browser = await webdriver(next.url, '/')
// layout
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-layout")).fontFamily'
)
).toMatch(/^__font1_.{6}$/)
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-layout")).fontWeight'
)
).toBe('400')
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-layout")).fontStyle'
)
).toBe('normal')
// page
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-page")).fontFamily'
)
).toMatch(/^__font2_.{6}$/)
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-page")).fontWeight'
)
).toBe('400')
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-page")).fontStyle'
)
).toBe('normal')
// Comp
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-comp")).fontFamily'
)
).toMatch(/^__font3_.{6}$/)
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-comp")).fontWeight'
)
).toBe('900')
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-comp")).fontStyle'
)
).toBe('italic')
})
it('should have correct styles at /client', async () => {
const browser = await webdriver(next.url, '/client')
// root layout
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-layout")).fontFamily'
)
).toMatch(/^__font1_.{6}$/)
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-layout")).fontWeight'
)
).toBe('400')
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-layout")).fontStyle'
)
).toBe('normal')
// layout
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#client-layout")).fontFamily'
)
).toMatch(/^__font4_.{6}$/)
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#client-layout")).fontWeight'
)
).toBe('100')
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#client-layout")).fontStyle'
)
).toBe('normal')
// page
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#client-page")).fontFamily'
)
).toMatch(/^__font5_.{6}$/)
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#client-page")).fontWeight'
)
).toBe('400')
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#client-page")).fontStyle'
)
).toBe('italic')
// Comp
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#client-comp")).fontFamily'
)
).toMatch(/^__font6_.{6}$/)
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#client-comp")).fontWeight'
)
).toBe('400')
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#client-comp")).fontStyle'
)
).toBe('normal')
})
})
if (!isDev) {
describe('preload', () => {
it('should preload correctly with server components', async () => {
const html = await renderViaHTTP(next.url, '/')
const $ = cheerio.load(html)
// Preconnect
expect($('link[rel="preconnect"]').length).toBe(0)
expect($('link[as="font"]').length).toBe(3)
expect($('link[as="font"]').get(0).attribs).toEqual({
as: 'font',
crossorigin: '',
href: '/_next/static/media/e9b9dc0d8ba35f48.p.woff2',
rel: 'preload',
type: 'font/woff2',
})
expect($('link[as="font"]').get(1).attribs).toEqual({
as: 'font',
crossorigin: '',
href: '/_next/static/media/b61859a50be14c53.p.woff2',
rel: 'preload',
type: 'font/woff2',
})
expect($('link[as="font"]').get(2).attribs).toEqual({
as: 'font',
crossorigin: '',
href: '/_next/static/media/b2104791981359ae.p.woff2',
rel: 'preload',
type: 'font/woff2',
})
})
it('should preload correctly with client components', async () => {
const html = await renderViaHTTP(next.url, '/client')
const $ = cheerio.load(html)
// Preconnect
expect($('link[rel="preconnect"]').length).toBe(0)
expect($('link[as="font"]').length).toBe(3)
// From root layout
expect($('link[as="font"]').get(0).attribs).toEqual({
as: 'font',
crossorigin: '',
href: '/_next/static/media/e9b9dc0d8ba35f48.p.woff2',
rel: 'preload',
type: 'font/woff2',
})
expect($('link[as="font"]').get(1).attribs).toEqual({
as: 'font',
crossorigin: '',
href: '/_next/static/media/e1053f04babc7571.p.woff2',
rel: 'preload',
type: 'font/woff2',
})
expect($('link[as="font"]').get(2).attribs).toEqual({
as: 'font',
crossorigin: '',
href: '/_next/static/media/feab2c68f2a8e9a4.p.woff2',
rel: 'preload',
type: 'font/woff2',
})
})
it('should preload correctly with layout using fonts', async () => {
const html = await renderViaHTTP(next.url, '/layout-with-fonts')
const $ = cheerio.load(html)
// Preconnect
expect($('link[rel="preconnect"]').length).toBe(0)
expect($('link[as="font"]').length).toBe(2)
// From root layout
expect($('link[as="font"]').get(0).attribs).toEqual({
as: 'font',
crossorigin: '',
href: '/_next/static/media/e9b9dc0d8ba35f48.p.woff2',
rel: 'preload',
type: 'font/woff2',
})
expect($('link[as="font"]').get(1).attribs).toEqual({
as: 'font',
crossorigin: '',
href: '/_next/static/media/75c5faeeb9c86969.p.woff2',
rel: 'preload',
type: 'font/woff2',
})
})
it('should preload correctly with page using fonts', async () => {
const html = await renderViaHTTP(next.url, '/page-with-fonts')
const $ = cheerio.load(html)
// Preconnect
expect($('link[rel="preconnect"]').length).toBe(0)
expect($('link[as="font"]').length).toBe(2)
// From root layout
expect($('link[as="font"]').get(0).attribs).toEqual({
as: 'font',
crossorigin: '',
href: '/_next/static/media/e9b9dc0d8ba35f48.p.woff2',
rel: 'preload',
type: 'font/woff2',
})
expect($('link[as="font"]').get(1).attribs).toEqual({
as: 'font',
crossorigin: '',
href: '/_next/static/media/568e4c6d8123c4d6.p.woff2',
rel: 'preload',
type: 'font/woff2',
})
})
})
}
if (isDev) {
describe('Dev errors', () => {
it('should recover on font loader error', async () => {
const browser = await webdriver(next.url, '/')
const font1Content = await next.readFile('fonts/index.js')
// Break file
await next.patchFile(
'fonts/index.js',
font1Content.replace('./font1.woff2', './does-not-exist.woff2')
)
expect(await hasRedbox(browser, true)).toBeTrue()
expect(await getRedboxSource(browser)).toInclude(
"Can't resolve './does-not-exist.woff2'"
)
// Fix file
await next.patchFile('fonts/index.js', font1Content)
await browser.waitForElementByCss('#root-page')
})
})
}
})
)

View file

@ -1,314 +1,273 @@
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { fetchViaHTTP, renderViaHTTP } from 'next-test-utils'
import cheerio from 'cheerio'
import { createNextDescribe } from 'e2e-utils'
import path from 'path'
import webdriver from 'next-webdriver'
describe('app dir next-image', () => {
if ((global as any).isNextDeploy) {
it('should skip next deploy for now', () => {})
return
createNextDescribe(
'app dir next-image',
{
files: path.join(__dirname, 'next-image'),
skipDeployment: true,
},
({ next }) => {
describe('ssr content', () => {
it('should render images on / route', async () => {
const $ = await next.render$('/')
const layout = $('#app-layout')
expect(layout.attr('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85'
)
expect(layout.attr('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=85 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85 2x'
)
const page = $('#app-page')
expect(page.attr('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=90'
)
expect(page.attr('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=90 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=90 2x'
)
const comp = $('#app-comp')
expect(comp.attr('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=80'
)
expect(comp.attr('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=80 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=80 2x'
)
})
it('should render images on /client route', async () => {
const $ = await next.render$('/client')
const root = $('#app-layout')
expect(root.attr('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85'
)
expect(root.attr('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=85 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85 2x'
)
const layout = $('#app-client-layout')
expect(layout.attr('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=55'
)
expect(layout.attr('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=55 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=55 2x'
)
const page = $('#app-client-page')
expect(page.attr('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=60'
)
expect(page.attr('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=60 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=60 2x'
)
const comp = $('#app-client-comp')
expect(comp.attr('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=50'
)
expect(comp.attr('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=50 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=50 2x'
)
})
it('should render images nested under page dir on /nested route', async () => {
const $ = await next.render$('/nested')
const root = $('#app-layout')
expect(root.attr('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85'
)
expect(root.attr('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=85 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85 2x'
)
const layout = $('#app-nested-layout')
expect(layout.attr('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=70'
)
expect(layout.attr('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=640&q=70 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=70 2x'
)
const page = $('#app-nested-page')
expect(page.attr('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=75'
)
expect(page.attr('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=640&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=75 2x'
)
const comp = $('#app-nested-comp')
expect(comp.attr('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=65'
)
expect(comp.attr('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=640&q=65 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=65 2x'
)
})
})
describe('browser content', () => {
it('should render images on / route', async () => {
const browser = await next.browser('/')
const layout = await browser.elementById('app-layout')
expect(await layout.getAttribute('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85'
)
expect(await layout.getAttribute('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=85 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85 2x'
)
const page = await browser.elementById('app-page')
expect(await page.getAttribute('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=90'
)
expect(await page.getAttribute('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=90 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=90 2x'
)
const comp = await browser.elementById('app-comp')
expect(await comp.getAttribute('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=80'
)
expect(await comp.getAttribute('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=80 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=80 2x'
)
})
it('should render images on /client route', async () => {
const browser = await next.browser('/client')
const root = await browser.elementById('app-layout')
expect(await root.getAttribute('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85'
)
expect(await root.getAttribute('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=85 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85 2x'
)
const layout = await browser.elementById('app-client-layout')
expect(await layout.getAttribute('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=55'
)
expect(await layout.getAttribute('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=55 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=55 2x'
)
const page = await browser.elementById('app-client-page')
expect(await page.getAttribute('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=60'
)
expect(await page.getAttribute('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=60 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=60 2x'
)
const comp = await browser.elementById('app-client-comp')
expect(await comp.getAttribute('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=50'
)
expect(await comp.getAttribute('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=50 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=50 2x'
)
})
it('should render images nested under page dir on /nested route', async () => {
const browser = await next.browser('/nested')
const root = await browser.elementById('app-layout')
expect(await root.getAttribute('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85'
)
expect(await root.getAttribute('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=85 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85 2x'
)
const layout = await browser.elementById('app-nested-layout')
expect(await layout.getAttribute('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=70'
)
expect(await layout.getAttribute('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=640&q=70 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=70 2x'
)
const page = await browser.elementById('app-nested-page')
expect(await page.getAttribute('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=75'
)
expect(await page.getAttribute('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=640&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=75 2x'
)
const comp = await browser.elementById('app-nested-comp')
expect(await comp.getAttribute('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=65'
)
expect(await comp.getAttribute('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=640&q=65 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=65 2x'
)
})
})
describe('image content', () => {
it('should render images on / route', async () => {
const $ = await next.render$('/')
const res1 = await next.fetch($('#app-layout').attr('src'))
expect(res1.status).toBe(200)
expect(res1.headers.get('content-type')).toBe('image/png')
const res2 = await next.fetch($('#app-page').attr('src'))
expect(res2.status).toBe(200)
expect(res2.headers.get('content-type')).toBe('image/png')
const res3 = await next.fetch($('#app-comp').attr('src'))
expect(res3.status).toBe(200)
expect(res3.headers.get('content-type')).toBe('image/png')
})
it('should render images on /client route', async () => {
const $ = await next.render$('/client')
const res1 = await next.fetch($('#app-layout').attr('src'))
expect(res1.status).toBe(200)
expect(res1.headers.get('content-type')).toBe('image/png')
const res2 = await next.fetch($('#app-client-layout').attr('src'))
expect(res2.status).toBe(200)
expect(res2.headers.get('content-type')).toBe('image/png')
const res3 = await next.fetch($('#app-client-page').attr('src'))
expect(res3.status).toBe(200)
expect(res3.headers.get('content-type')).toBe('image/png')
const res4 = await next.fetch($('#app-client-comp').attr('src'))
expect(res4.status).toBe(200)
expect(res4.headers.get('content-type')).toBe('image/png')
})
it('should render images nested under page dir on /nested route', async () => {
const $ = await next.render$('/nested')
const res1 = await next.fetch($('#app-layout').attr('src'))
expect(res1.status).toBe(200)
expect(res1.headers.get('content-type')).toBe('image/png')
const res2 = await next.fetch($('#app-nested-layout').attr('src'))
expect(res2.status).toBe(200)
expect(res2.headers.get('content-type')).toBe('image/jpeg')
const res3 = await next.fetch($('#app-nested-page').attr('src'))
expect(res3.status).toBe(200)
expect(res3.headers.get('content-type')).toBe('image/jpeg')
const res4 = await next.fetch($('#app-nested-comp').attr('src'))
expect(res4.status).toBe(200)
expect(res4.headers.get('content-type')).toBe('image/jpeg')
})
})
}
let next: NextInstance
beforeAll(async () => {
next = await createNext({
files: new FileRef(path.join(__dirname, 'next-image')),
dependencies: {
react: 'latest',
'react-dom': 'latest',
},
skipStart: true,
})
await next.start()
})
afterAll(() => next.destroy())
describe('ssr content', () => {
it('should render images on / route', async () => {
const html = await renderViaHTTP(next.url, '/')
const $ = cheerio.load(html)
const layout = $('#app-layout')
expect(layout.attr('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85'
)
expect(layout.attr('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=85 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85 2x'
)
const page = $('#app-page')
expect(page.attr('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=90'
)
expect(page.attr('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=90 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=90 2x'
)
const comp = $('#app-comp')
expect(comp.attr('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=80'
)
expect(comp.attr('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=80 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=80 2x'
)
})
it('should render images on /client route', async () => {
const html = await renderViaHTTP(next.url, '/client')
const $ = cheerio.load(html)
const root = $('#app-layout')
expect(root.attr('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85'
)
expect(root.attr('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=85 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85 2x'
)
const layout = $('#app-client-layout')
expect(layout.attr('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=55'
)
expect(layout.attr('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=55 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=55 2x'
)
const page = $('#app-client-page')
expect(page.attr('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=60'
)
expect(page.attr('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=60 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=60 2x'
)
const comp = $('#app-client-comp')
expect(comp.attr('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=50'
)
expect(comp.attr('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=50 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=50 2x'
)
})
it('should render images nested under page dir on /nested route', async () => {
const html = await renderViaHTTP(next.url, '/nested')
const $ = cheerio.load(html)
const root = $('#app-layout')
expect(root.attr('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85'
)
expect(root.attr('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=85 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85 2x'
)
const layout = $('#app-nested-layout')
expect(layout.attr('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=70'
)
expect(layout.attr('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=640&q=70 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=70 2x'
)
const page = $('#app-nested-page')
expect(page.attr('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=75'
)
expect(page.attr('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=640&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=75 2x'
)
const comp = $('#app-nested-comp')
expect(comp.attr('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=65'
)
expect(comp.attr('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=640&q=65 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=65 2x'
)
})
})
describe('browser content', () => {
it('should render images on / route', async () => {
const browser = await webdriver(next.url, '/')
const layout = await browser.elementById('app-layout')
expect(await layout.getAttribute('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85'
)
expect(await layout.getAttribute('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=85 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85 2x'
)
const page = await browser.elementById('app-page')
expect(await page.getAttribute('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=90'
)
expect(await page.getAttribute('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=90 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=90 2x'
)
const comp = await browser.elementById('app-comp')
expect(await comp.getAttribute('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=80'
)
expect(await comp.getAttribute('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=80 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=80 2x'
)
})
it('should render images on /client route', async () => {
const browser = await webdriver(next.url, '/client')
const root = await browser.elementById('app-layout')
expect(await root.getAttribute('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85'
)
expect(await root.getAttribute('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=85 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85 2x'
)
const layout = await browser.elementById('app-client-layout')
expect(await layout.getAttribute('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=55'
)
expect(await layout.getAttribute('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=55 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=55 2x'
)
const page = await browser.elementById('app-client-page')
expect(await page.getAttribute('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=60'
)
expect(await page.getAttribute('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=60 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=60 2x'
)
const comp = await browser.elementById('app-client-comp')
expect(await comp.getAttribute('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=50'
)
expect(await comp.getAttribute('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=50 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=50 2x'
)
})
it('should render images nested under page dir on /nested route', async () => {
const browser = await webdriver(next.url, '/nested')
const root = await browser.elementById('app-layout')
expect(await root.getAttribute('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85'
)
expect(await root.getAttribute('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=640&q=85 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=828&q=85 2x'
)
const layout = await browser.elementById('app-nested-layout')
expect(await layout.getAttribute('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=70'
)
expect(await layout.getAttribute('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=640&q=70 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=70 2x'
)
const page = await browser.elementById('app-nested-page')
expect(await page.getAttribute('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=75'
)
expect(await page.getAttribute('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=640&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=75 2x'
)
const comp = await browser.elementById('app-nested-comp')
expect(await comp.getAttribute('src')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=65'
)
expect(await comp.getAttribute('srcset')).toBe(
'/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=640&q=65 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.fab2915d.jpg&w=828&q=65 2x'
)
})
})
describe('image content', () => {
it('should render images on / route', async () => {
const html = await renderViaHTTP(next.url, '/')
const $ = cheerio.load(html)
const res1 = await fetchViaHTTP(next.url, $('#app-layout').attr('src'))
expect(res1.status).toBe(200)
expect(res1.headers.get('content-type')).toBe('image/png')
const res2 = await fetchViaHTTP(next.url, $('#app-page').attr('src'))
expect(res2.status).toBe(200)
expect(res2.headers.get('content-type')).toBe('image/png')
const res3 = await fetchViaHTTP(next.url, $('#app-comp').attr('src'))
expect(res3.status).toBe(200)
expect(res3.headers.get('content-type')).toBe('image/png')
})
it('should render images on /client route', async () => {
const html = await renderViaHTTP(next.url, '/client')
const $ = cheerio.load(html)
const res1 = await fetchViaHTTP(next.url, $('#app-layout').attr('src'))
expect(res1.status).toBe(200)
expect(res1.headers.get('content-type')).toBe('image/png')
const res2 = await fetchViaHTTP(
next.url,
$('#app-client-layout').attr('src')
)
expect(res2.status).toBe(200)
expect(res2.headers.get('content-type')).toBe('image/png')
const res3 = await fetchViaHTTP(
next.url,
$('#app-client-page').attr('src')
)
expect(res3.status).toBe(200)
expect(res3.headers.get('content-type')).toBe('image/png')
const res4 = await fetchViaHTTP(
next.url,
$('#app-client-comp').attr('src')
)
expect(res4.status).toBe(200)
expect(res4.headers.get('content-type')).toBe('image/png')
})
it('should render images nested under page dir on /nested route', async () => {
const html = await renderViaHTTP(next.url, '/nested')
const $ = cheerio.load(html)
const res1 = await fetchViaHTTP(next.url, $('#app-layout').attr('src'))
expect(res1.status).toBe(200)
expect(res1.headers.get('content-type')).toBe('image/png')
const res2 = await fetchViaHTTP(
next.url,
$('#app-nested-layout').attr('src')
)
expect(res2.status).toBe(200)
expect(res2.headers.get('content-type')).toBe('image/jpeg')
const res3 = await fetchViaHTTP(
next.url,
$('#app-nested-page').attr('src')
)
expect(res3.status).toBe(200)
expect(res3.headers.get('content-type')).toBe('image/jpeg')
const res4 = await fetchViaHTTP(
next.url,
$('#app-nested-comp').attr('src')
)
expect(res4.status).toBe(200)
expect(res4.headers.get('content-type')).toBe('image/jpeg')
})
})
})
)

View file

@ -1,63 +1,53 @@
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { createNextDescribe } from 'e2e-utils'
import { waitFor } from 'next-test-utils'
import path from 'path'
import webdriver from 'next-webdriver'
describe('app dir prefetching', () => {
// TODO: re-enable for dev after https://vercel.slack.com/archives/C035J346QQL/p1663822388387959 is resolved (Sep 22nd 2022)
if ((global as any).isNextDeploy || (global as any).isNextDev) {
it('should skip next deploy for now', () => {})
return
}
createNextDescribe(
'app dir prefetching',
{
files: path.join(__dirname, 'app-prefetch'),
skipDeployment: true,
},
({ next, isNextDev }) => {
// TODO: re-enable for dev after https://vercel.slack.com/archives/C035J346QQL/p1663822388387959 is resolved (Sep 22nd 2022)
if (isNextDev) {
it('should skip next deploy for now', () => {})
return
}
let next: NextInstance
it('should show layout eagerly when prefetched with loading one level down', async () => {
const browser = await next.browser('/')
// Ensure the page is prefetched
await waitFor(1000)
beforeAll(async () => {
next = await createNext({
files: new FileRef(path.join(__dirname, 'app-prefetch')),
dependencies: {
react: 'latest',
'react-dom': 'latest',
},
skipStart: true,
const before = Date.now()
await browser
.elementByCss('#to-dashboard')
.click()
.waitForElementByCss('#dashboard-layout')
const after = Date.now()
const timeToComplete = after - before
expect(timeToComplete < 1000).toBe(true)
expect(await browser.elementByCss('#dashboard-layout').text()).toBe(
'Dashboard Hello World'
)
await browser.waitForElementByCss('#dashboard-page')
expect(await browser.waitForElementByCss('#dashboard-page').text()).toBe(
'Welcome to the dashboard'
)
})
await next.start()
})
afterAll(() => next.destroy())
it('should show layout eagerly when prefetched with loading one level down', async () => {
const browser = await webdriver(next.url, '/')
// Ensure the page is prefetched
await waitFor(1000)
const before = Date.now()
await browser
.elementByCss('#to-dashboard')
.click()
.waitForElementByCss('#dashboard-layout')
const after = Date.now()
const timeToComplete = after - before
expect(timeToComplete < 1000).toBe(true)
expect(await browser.elementByCss('#dashboard-layout').text()).toBe(
'Dashboard Hello World'
)
await browser.waitForElementByCss('#dashboard-page')
expect(await browser.waitForElementByCss('#dashboard-page').text()).toBe(
'Welcome to the dashboard'
)
})
it('should not have prefetch error for static path', async () => {
const browser = await webdriver(next.url, '/')
await browser.eval('window.nd.router.prefetch("/dashboard/123")')
await waitFor(3000)
await browser.eval('window.nd.router.push("/dashboard/123")')
expect(next.cliOutput).not.toContain('ReferenceError')
expect(next.cliOutput).not.toContain('is not defined')
})
})
it('should not have prefetch error for static path', async () => {
const browser = await next.browser('/')
await browser.eval('window.nd.router.prefetch("/dashboard/123")')
await waitFor(3000)
await browser.eval('window.nd.router.push("/dashboard/123")')
expect(next.cliOutput).not.toContain('ReferenceError')
expect(next.cliOutput).not.toContain('is not defined')
})
}
)

View file

@ -1,147 +1,130 @@
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { renderViaHTTP, fetchViaHTTP, waitFor } from 'next-test-utils'
import { createNextDescribe } from 'e2e-utils'
import { waitFor } from 'next-test-utils'
import path from 'path'
import cheerio from 'cheerio'
describe('app dir rendering', () => {
if ((global as any).isNextDeploy) {
it('should skip next deploy for now', () => {})
return
createNextDescribe(
'app dir rendering',
{
files: path.join(__dirname, 'app-rendering'),
skipDeployment: true,
},
({ next, isNextDev: isDev }) => {
it('should serve app/page.server.js at /', async () => {
const html = await next.render('/')
expect(html).toContain('app/page.server.js')
})
describe('SSR only', () => {
it('should run data in layout and page', async () => {
const $ = await next.render$('/ssr-only/nested')
expect($('#layout-message').text()).toBe('hello from layout')
expect($('#page-message').text()).toBe('hello from page')
})
it('should run data fetch in parallel', async () => {
const startTime = Date.now()
const $ = await next.render$('/ssr-only/slow')
const endTime = Date.now()
const duration = endTime - startTime
// Each part takes 5 seconds so it should be below 10 seconds
// Using 7 seconds to ensure external factors causing slight slowness don't fail the tests
expect(duration < 7000).toBe(true)
expect($('#slow-layout-message').text()).toBe('hello from slow layout')
expect($('#slow-page-message').text()).toBe('hello from slow page')
})
})
describe('static only', () => {
it('should run data in layout and page', async () => {
const $ = await next.render$('/static-only/nested')
expect($('#layout-message').text()).toBe('hello from layout')
expect($('#page-message').text()).toBe('hello from page')
})
it(`should run data in parallel ${
isDev ? 'during development' : 'and use cached version for production'
}`, async () => {
// const startTime = Date.now()
const $ = await next.render$('/static-only/slow')
// const endTime = Date.now()
// const duration = endTime - startTime
// Each part takes 5 seconds so it should be below 10 seconds
// Using 7 seconds to ensure external factors causing slight slowness don't fail the tests
// TODO: cache static props in prod
// expect(duration < (isDev ? 7000 : 2000)).toBe(true)
// expect(duration < 7000).toBe(true)
expect($('#slow-layout-message').text()).toBe('hello from slow layout')
expect($('#slow-page-message').text()).toBe('hello from slow page')
})
})
describe('ISR', () => {
it('should revalidate the page when revalidate is configured', async () => {
const getPage = async () => {
const res = await next.fetch('isr-multiple/nested')
const html = await res.text()
return {
$: cheerio.load(html),
cacheHeader: res.headers['x-nextjs-cache'],
}
}
const { $ } = await getPage()
expect($('#layout-message').text()).toBe('hello from layout')
expect($('#page-message').text()).toBe('hello from page')
const layoutNow = $('#layout-now').text()
const pageNow = $('#page-now').text()
await waitFor(2000)
// TODO: implement
// Trigger revalidate
// const { cacheHeader: revalidateCacheHeader } = await getPage()
// expect(revalidateCacheHeader).toBe('STALE')
// TODO: implement
const { $: $revalidated /* cacheHeader: revalidatedCacheHeader */ } =
await getPage()
// expect(revalidatedCacheHeader).toBe('REVALIDATED')
const layoutNowRevalidated = $revalidated('#layout-now').text()
const pageNowRevalidated = $revalidated('#page-now').text()
// Expect that the `Date.now()` is different as the page have been regenerated
expect(layoutNow).not.toBe(layoutNowRevalidated)
expect(pageNow).not.toBe(pageNowRevalidated)
})
})
// TODO: implement
describe.skip('mixed static and dynamic', () => {
it('should generate static data during build and use it', async () => {
const getPage = async () => {
const $ = await next.render$('isr-ssr-combined/nested')
return {
$,
}
}
const { $ } = await getPage()
expect($('#layout-message').text()).toBe('hello from layout')
expect($('#page-message').text()).toBe('hello from page')
const layoutNow = $('#layout-now').text()
const pageNow = $('#page-now').text()
const { $: $second } = await getPage()
const layoutNowSecond = $second('#layout-now').text()
const pageNowSecond = $second('#page-now').text()
// Expect that the `Date.now()` is different as it came from getServerSideProps
expect(layoutNow).not.toBe(layoutNowSecond)
// Expect that the `Date.now()` is the same as it came from getStaticProps
expect(pageNow).toBe(pageNowSecond)
})
})
}
const isDev = (global as any).isNextDev
let next: NextInstance
beforeAll(async () => {
next = await createNext({
files: new FileRef(path.join(__dirname, 'app-rendering')),
dependencies: {
react: 'latest',
'react-dom': 'latest',
},
})
})
afterAll(() => next.destroy())
it('should serve app/page.server.js at /', async () => {
const html = await renderViaHTTP(next.url, '/')
expect(html).toContain('app/page.server.js')
})
describe('SSR only', () => {
it('should run data in layout and page', async () => {
const html = await renderViaHTTP(next.url, '/ssr-only/nested')
const $ = cheerio.load(html)
expect($('#layout-message').text()).toBe('hello from layout')
expect($('#page-message').text()).toBe('hello from page')
})
it('should run data fetch in parallel', async () => {
const startTime = Date.now()
const html = await renderViaHTTP(next.url, '/ssr-only/slow')
const endTime = Date.now()
const duration = endTime - startTime
// Each part takes 5 seconds so it should be below 10 seconds
// Using 7 seconds to ensure external factors causing slight slowness don't fail the tests
expect(duration < 7000).toBe(true)
const $ = cheerio.load(html)
expect($('#slow-layout-message').text()).toBe('hello from slow layout')
expect($('#slow-page-message').text()).toBe('hello from slow page')
})
})
describe('static only', () => {
it('should run data in layout and page', async () => {
const html = await renderViaHTTP(next.url, '/static-only/nested')
const $ = cheerio.load(html)
expect($('#layout-message').text()).toBe('hello from layout')
expect($('#page-message').text()).toBe('hello from page')
})
it(`should run data in parallel ${
isDev ? 'during development' : 'and use cached version for production'
}`, async () => {
// const startTime = Date.now()
const html = await renderViaHTTP(next.url, '/static-only/slow')
// const endTime = Date.now()
// const duration = endTime - startTime
// Each part takes 5 seconds so it should be below 10 seconds
// Using 7 seconds to ensure external factors causing slight slowness don't fail the tests
// TODO: cache static props in prod
// expect(duration < (isDev ? 7000 : 2000)).toBe(true)
// expect(duration < 7000).toBe(true)
const $ = cheerio.load(html)
expect($('#slow-layout-message').text()).toBe('hello from slow layout')
expect($('#slow-page-message').text()).toBe('hello from slow page')
})
})
describe('ISR', () => {
it('should revalidate the page when revalidate is configured', async () => {
const getPage = async () => {
const res = await fetchViaHTTP(next.url, 'isr-multiple/nested')
const html = await res.text()
return {
$: cheerio.load(html),
cacheHeader: res.headers['x-nextjs-cache'],
}
}
const { $ } = await getPage()
expect($('#layout-message').text()).toBe('hello from layout')
expect($('#page-message').text()).toBe('hello from page')
const layoutNow = $('#layout-now').text()
const pageNow = $('#page-now').text()
await waitFor(2000)
// TODO: implement
// Trigger revalidate
// const { cacheHeader: revalidateCacheHeader } = await getPage()
// expect(revalidateCacheHeader).toBe('STALE')
// TODO: implement
const { $: $revalidated /* cacheHeader: revalidatedCacheHeader */ } =
await getPage()
// expect(revalidatedCacheHeader).toBe('REVALIDATED')
const layoutNowRevalidated = $revalidated('#layout-now').text()
const pageNowRevalidated = $revalidated('#page-now').text()
// Expect that the `Date.now()` is different as the page have been regenerated
expect(layoutNow).not.toBe(layoutNowRevalidated)
expect(pageNow).not.toBe(pageNowRevalidated)
})
})
// TODO: implement
describe.skip('mixed static and dynamic', () => {
it('should generate static data during build and use it', async () => {
const getPage = async () => {
const html = await renderViaHTTP(next.url, 'isr-ssr-combined/nested')
return {
$: cheerio.load(html),
}
}
const { $ } = await getPage()
expect($('#layout-message').text()).toBe('hello from layout')
expect($('#page-message').text()).toBe('hello from page')
const layoutNow = $('#layout-now').text()
const pageNow = $('#page-now').text()
const { $: $second } = await getPage()
const layoutNowSecond = $second('#layout-now').text()
const pageNowSecond = $second('#page-now').text()
// Expect that the `Date.now()` is different as it came from getServerSideProps
expect(layoutNow).not.toBe(layoutNowSecond)
// Expect that the `Date.now()` is the same as it came from getStaticProps
expect(pageNow).toBe(pageNowSecond)
})
})
})
)

View file

@ -1,219 +1,199 @@
import path from 'path'
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import webdriver from 'next-webdriver'
import { createNextDescribe } from 'e2e-utils'
import { getRedboxSource, hasRedbox } from 'next-test-utils'
describe('app-dir root layout', () => {
const isDev = (global as any).isNextDev
createNextDescribe(
'app-dir root layout',
{
files: path.join(__dirname, 'root-layout'),
skipDeployment: true,
},
({ next, isNextDev: isDev }) => {
if (isDev) {
// TODO-APP: re-enable after reworking the error overlay.
describe.skip('Missing required tags', () => {
it('should error on page load', async () => {
const browser = await next.browser('/missing-tags', {
waitHydration: false,
})
if ((global as any).isNextDeploy) {
it('should skip next deploy for now', () => {})
return
}
let next: NextInstance
beforeAll(async () => {
next = await createNext({
files: {
app: new FileRef(path.join(__dirname, 'root-layout/app')),
'next.config.js': new FileRef(
path.join(__dirname, 'root-layout/next.config.js')
),
},
dependencies: {
react: 'latest',
'react-dom': 'latest',
},
})
})
afterAll(() => next.destroy())
if (isDev) {
// TODO-APP: re-enable after reworking the error overlay.
describe.skip('Missing required tags', () => {
it('should error on page load', async () => {
const browser = await webdriver(next.url, '/missing-tags', {
waitHydration: false,
})
expect(await hasRedbox(browser, true)).toBe(true)
expect(await getRedboxSource(browser)).toMatchInlineSnapshot(`
expect(await hasRedbox(browser, true)).toBe(true)
expect(await getRedboxSource(browser)).toMatchInlineSnapshot(`
"Please make sure to include the following tags in your root layout: <html>, <body>.
Missing required root layout tags: html, body"
`)
})
it('should error on page navigation', async () => {
const browser = await webdriver(next.url, '/has-tags', {
waitHydration: false,
})
it('should error on page navigation', async () => {
const browser = await next.browser('/has-tags', {
waitHydration: false,
})
await browser.elementByCss('a').click()
expect(await hasRedbox(browser, true)).toBe(true)
expect(await getRedboxSource(browser)).toMatchInlineSnapshot(`
"Please make sure to include the following tags in your root layout: <html>, <body>.
Missing required root layout tags: html, body"
`)
})
it('should error on page load on static generation', async () => {
const browser = await next.browser('/static-missing-tags/slug', {
waitHydration: false,
})
expect(await hasRedbox(browser, true)).toBe(true)
expect(await getRedboxSource(browser)).toMatchInlineSnapshot(`
"Please make sure to include the following tags in your root layout: <html>, <body>.
Missing required root layout tags: html, body"
`)
})
})
}
describe('Should do a mpa navigation when switching root layout', () => {
it('should work with basic routes', async () => {
const browser = await next.browser('/basic-route')
expect(await browser.elementById('basic-route').text()).toBe(
'Basic route'
)
await browser.eval('window.__TEST_NO_RELOAD = true')
// Navigate to page with same root layout
await browser.elementByCss('a').click()
expect(
await browser.waitForElementByCss('#inner-basic-route').text()
).toBe('Inner basic route')
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeTrue()
expect(await hasRedbox(browser, true)).toBe(true)
expect(await getRedboxSource(browser)).toMatchInlineSnapshot(`
"Please make sure to include the following tags in your root layout: <html>, <body>.
Missing required root layout tags: html, body"
`)
// Navigate to page with different root layout
await browser.elementByCss('a').click()
expect(await browser.waitForElementByCss('#route-group').text()).toBe(
'Route group'
)
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeUndefined()
})
it('should error on page load on static generation', async () => {
const browser = await webdriver(next.url, '/static-missing-tags/slug', {
waitHydration: false,
})
it('should work with route groups', async () => {
const browser = await next.browser('/route-group')
expect(await hasRedbox(browser, true)).toBe(true)
expect(await getRedboxSource(browser)).toMatchInlineSnapshot(`
"Please make sure to include the following tags in your root layout: <html>, <body>.
expect(await browser.elementById('route-group').text()).toBe(
'Route group'
)
await browser.eval('window.__TEST_NO_RELOAD = true')
Missing required root layout tags: html, body"
`)
// Navigate to page with same root layout
await browser.elementByCss('a').click()
expect(
await browser.waitForElementByCss('#nested-route-group').text()
).toBe('Nested route group')
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeTrue()
// Navigate to page with different root layout
await browser.elementByCss('a').click()
expect(await browser.waitForElementByCss('#parallel-one').text()).toBe(
'One'
)
expect(await browser.waitForElementByCss('#parallel-two').text()).toBe(
'Two'
)
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeUndefined()
})
it('should work with parallel routes', async () => {
const browser = await next.browser('/with-parallel-routes')
expect(await browser.elementById('parallel-one').text()).toBe('One')
expect(await browser.elementById('parallel-two').text()).toBe('Two')
await browser.eval('window.__TEST_NO_RELOAD = true')
// Navigate to page with same root layout
await browser.elementByCss('a').click()
expect(
await browser.waitForElementByCss('#parallel-one-inner').text()
).toBe('One inner')
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeTrue()
// Navigate to page with different root layout
await browser.elementByCss('a').click()
expect(await browser.waitForElementByCss('#dynamic-hello').text()).toBe(
'dynamic hello'
)
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeUndefined()
})
it('should work with dynamic routes', async () => {
const browser = await next.browser('/dynamic/first')
expect(await browser.elementById('dynamic-first').text()).toBe(
'dynamic first'
)
await browser.eval('window.__TEST_NO_RELOAD = true')
// Navigate to page with same root layout
await browser.elementByCss('a').click()
expect(
await browser.waitForElementByCss('#dynamic-first-second').text()
).toBe('dynamic first second')
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeTrue()
// Navigate to page with different root layout
await browser.elementByCss('a').click()
expect(
await browser.waitForElementByCss('#inner-basic-route').text()
).toBe('Inner basic route')
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeUndefined()
})
it('should work with dynamic catchall routes', async () => {
const browser = await next.browser('/dynamic-catchall/slug')
expect(await browser.elementById('catchall-slug').text()).toBe(
'catchall slug'
)
await browser.eval('window.__TEST_NO_RELOAD = true')
// Navigate to page with same root layout
await browser.elementById('to-next-url').click()
expect(
await browser.waitForElementByCss('#catchall-slug-slug').text()
).toBe('catchall slug slug')
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeTrue()
// Navigate to page with different root layout
await browser.elementById('to-dynamic-first').click()
expect(await browser.elementById('dynamic-first').text()).toBe(
'dynamic first'
)
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeUndefined()
})
it('should work with static routes', async () => {
const browser = await next.browser('/static-mpa-navigation/slug1')
expect(await browser.elementById('static-slug1').text()).toBe(
'static slug1'
)
await browser.eval('window.__TEST_NO_RELOAD = true')
// Navigate to page with same root layout
await browser.elementByCss('a').click()
expect(await browser.waitForElementByCss('#static-slug2').text()).toBe(
'static slug2'
)
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeTrue()
// Navigate to page with different root layout
await browser.elementByCss('a').click()
expect(await browser.elementById('basic-route').text()).toBe(
'Basic route'
)
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeUndefined()
})
})
}
describe('Should do a mpa navigation when switching root layout', () => {
it('should work with basic routes', async () => {
const browser = await webdriver(next.url, '/basic-route')
expect(await browser.elementById('basic-route').text()).toBe(
'Basic route'
)
await browser.eval('window.__TEST_NO_RELOAD = true')
// Navigate to page with same root layout
await browser.elementByCss('a').click()
expect(
await browser.waitForElementByCss('#inner-basic-route').text()
).toBe('Inner basic route')
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeTrue()
// Navigate to page with different root layout
await browser.elementByCss('a').click()
expect(await browser.waitForElementByCss('#route-group').text()).toBe(
'Route group'
)
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeUndefined()
})
it('should work with route groups', async () => {
const browser = await webdriver(next.url, '/route-group')
expect(await browser.elementById('route-group').text()).toBe(
'Route group'
)
await browser.eval('window.__TEST_NO_RELOAD = true')
// Navigate to page with same root layout
await browser.elementByCss('a').click()
expect(
await browser.waitForElementByCss('#nested-route-group').text()
).toBe('Nested route group')
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeTrue()
// Navigate to page with different root layout
await browser.elementByCss('a').click()
expect(await browser.waitForElementByCss('#parallel-one').text()).toBe(
'One'
)
expect(await browser.waitForElementByCss('#parallel-two').text()).toBe(
'Two'
)
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeUndefined()
})
it('should work with parallel routes', async () => {
const browser = await webdriver(next.url, '/with-parallel-routes')
expect(await browser.elementById('parallel-one').text()).toBe('One')
expect(await browser.elementById('parallel-two').text()).toBe('Two')
await browser.eval('window.__TEST_NO_RELOAD = true')
// Navigate to page with same root layout
await browser.elementByCss('a').click()
expect(
await browser.waitForElementByCss('#parallel-one-inner').text()
).toBe('One inner')
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeTrue()
// Navigate to page with different root layout
await browser.elementByCss('a').click()
expect(await browser.waitForElementByCss('#dynamic-hello').text()).toBe(
'dynamic hello'
)
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeUndefined()
})
it('should work with dynamic routes', async () => {
const browser = await webdriver(next.url, '/dynamic/first')
expect(await browser.elementById('dynamic-first').text()).toBe(
'dynamic first'
)
await browser.eval('window.__TEST_NO_RELOAD = true')
// Navigate to page with same root layout
await browser.elementByCss('a').click()
expect(
await browser.waitForElementByCss('#dynamic-first-second').text()
).toBe('dynamic first second')
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeTrue()
// Navigate to page with different root layout
await browser.elementByCss('a').click()
expect(
await browser.waitForElementByCss('#inner-basic-route').text()
).toBe('Inner basic route')
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeUndefined()
})
it('should work with dynamic catchall routes', async () => {
const browser = await webdriver(next.url, '/dynamic-catchall/slug')
expect(await browser.elementById('catchall-slug').text()).toBe(
'catchall slug'
)
await browser.eval('window.__TEST_NO_RELOAD = true')
// Navigate to page with same root layout
await browser.elementById('to-next-url').click()
expect(
await browser.waitForElementByCss('#catchall-slug-slug').text()
).toBe('catchall slug slug')
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeTrue()
// Navigate to page with different root layout
await browser.elementById('to-dynamic-first').click()
expect(await browser.elementById('dynamic-first').text()).toBe(
'dynamic first'
)
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeUndefined()
})
it('should work with static routes', async () => {
const browser = await webdriver(next.url, '/static-mpa-navigation/slug1')
expect(await browser.elementById('static-slug1').text()).toBe(
'static slug1'
)
await browser.eval('window.__TEST_NO_RELOAD = true')
// Navigate to page with same root layout
await browser.elementByCss('a').click()
expect(await browser.waitForElementByCss('#static-slug2').text()).toBe(
'static slug2'
)
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeTrue()
// Navigate to page with different root layout
await browser.elementByCss('a').click()
expect(await browser.elementById('basic-route').text()).toBe(
'Basic route'
)
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeUndefined()
})
})
})
)

View file

@ -1,16 +1,10 @@
import path from 'path'
import fs from 'fs-extra'
import webdriver from 'next-webdriver'
import { renderViaHTTP, fetchViaHTTP, check } from 'next-test-utils'
import { check } from 'next-test-utils'
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import cheerio from 'cheerio'
function getNodeBySelector(html, selector) {
const $ = cheerio.load(html)
return $(selector)
}
async function resolveStreamResponse(response: any, onData?: any) {
let result = ''
onData = onData || (() => {})
@ -58,53 +52,49 @@ describe('app dir - rsc basics', () => {
afterAll(() => next.destroy())
const { isNextDeploy, isNextDev } = global as any
const isReact17 = process.env.NEXT_TEST_REACT_VERSION === '^17'
if (isNextDeploy || isReact17) {
if (isNextDeploy) {
it('should skip tests for next-deploy and react 17', () => {})
return
}
it('should correctly render page returning null', async () => {
const homeHTML = await renderViaHTTP(next.url, '/return-null/page')
const homeHTML = await next.render('/return-null/page')
const $ = cheerio.load(homeHTML)
expect($('#return-null-layout').html()).toBeEmpty()
})
it('should correctly render component returning null', async () => {
const homeHTML = await renderViaHTTP(next.url, '/return-null/component')
const homeHTML = await next.render('/return-null/component')
const $ = cheerio.load(homeHTML)
expect($('#return-null-layout').html()).toBeEmpty()
})
it('should correctly render layout returning null', async () => {
const homeHTML = await renderViaHTTP(next.url, '/return-null/layout')
const homeHTML = await next.render('/return-null/layout')
const $ = cheerio.load(homeHTML)
expect($('#return-null-layout').html()).toBeEmpty()
})
it('should correctly render page returning undefined', async () => {
const homeHTML = await renderViaHTTP(next.url, '/return-undefined/page')
const homeHTML = await next.render('/return-undefined/page')
const $ = cheerio.load(homeHTML)
expect($('#return-undefined-layout').html()).toBeEmpty()
})
it('should correctly render component returning undefined', async () => {
const homeHTML = await renderViaHTTP(
next.url,
'/return-undefined/component'
)
const homeHTML = await next.render('/return-undefined/component')
const $ = cheerio.load(homeHTML)
expect($('#return-undefined-layout').html()).toBeEmpty()
})
it('should correctly render layout returning undefined', async () => {
const homeHTML = await renderViaHTTP(next.url, '/return-undefined/layout')
const homeHTML = await next.render('/return-undefined/layout')
const $ = cheerio.load(homeHTML)
expect($('#return-undefined-layout').html()).toBeEmpty()
})
it('should render server components correctly', async () => {
const homeHTML = await renderViaHTTP(next.url, '/', null, {
const homeHTML = await next.render('/', null, {
headers: {
'x-next-test-client': 'test-util',
},
@ -142,7 +132,7 @@ describe('app dir - rsc basics', () => {
it('should reuse the inline flight response without sending extra requests', async () => {
let hasFlightRequest = false
let requestsCount = 0
await webdriver(next.url, '/root', {
await next.browser('/root', {
beforePageLoad(page) {
page.on('request', (request) => {
requestsCount++
@ -164,13 +154,13 @@ describe('app dir - rsc basics', () => {
})
it('should support multi-level server component imports', async () => {
const html = await renderViaHTTP(next.url, '/multi')
const html = await next.render('/multi')
expect(html).toContain('bar.server.js:')
expect(html).toContain('foo.client')
})
it('should be able to navigate between rsc routes', async () => {
const browser = await webdriver(next.url, '/root')
const browser = await next.browser('/root')
await browser.waitForElementByCss('#goto-next-link').click()
await new Promise((res) => setTimeout(res, 1000))
@ -192,7 +182,7 @@ describe('app dir - rsc basics', () => {
})
it('should handle streaming server components correctly', async () => {
const browser = await webdriver(next.url, '/streaming-rsc')
const browser = await next.browser('/streaming-rsc')
const content = await browser.eval(
`document.querySelector('#content').innerText`
)
@ -200,12 +190,12 @@ describe('app dir - rsc basics', () => {
})
it('should support next/link in server components', async () => {
const linkHTML = await renderViaHTTP(next.url, '/next-api/link')
const linkText = getNodeBySelector(linkHTML, 'body a[href="/root"]').text()
const $ = await next.render$('/next-api/link')
const linkText = $('body a[href="/root"]').text()
expect(linkText).toContain('home')
const browser = await webdriver(next.url, '/next-api/link')
const browser = await next.browser('/next-api/link')
// We need to make sure the app is fully hydrated before clicking, otherwise
// it will be a full redirection instead of being taken over by the next
@ -228,7 +218,7 @@ describe('app dir - rsc basics', () => {
it('should link correctly with next/link without mpa navigation to the page', async () => {
// Select the button which is not hidden but rendered
const selector = '#goto-next-link'
const browser = await webdriver(next.url, '/root', {})
const browser = await next.browser('/root', {})
await browser.eval('window.didNotReloadPage = true')
await browser.elementByCss(selector).click().waitForElementByCss('#query')
@ -240,13 +230,13 @@ describe('app dir - rsc basics', () => {
})
it('should escape streaming data correctly', async () => {
const browser = await webdriver(next.url, '/escaping-rsc')
const browser = await next.browser('/escaping-rsc')
const manipulated = await browser.eval(`window.__manipulated_by_injection`)
expect(manipulated).toBe(undefined)
})
it('should render built-in 404 page for missing route if pagesDir is not presented', async () => {
const res = await fetchViaHTTP(next.url, '/does-not-exist')
const res = await next.fetch('/does-not-exist')
expect(res.status).toBe(404)
const html = await res.text()
@ -254,28 +244,28 @@ describe('app dir - rsc basics', () => {
})
it('should suspense next/legacy/image in server components', async () => {
const imageHTML = await renderViaHTTP(next.url, '/next-api/image-legacy')
const imageTag = getNodeBySelector(imageHTML, '#myimg')
const $ = await next.render$('/next-api/image-legacy')
const imageTag = $('#myimg')
expect(imageTag.attr('src')).toContain('data:image')
})
it('should suspense next/image in server components', async () => {
const imageHTML = await renderViaHTTP(next.url, '/next-api/image-new')
const imageTag = getNodeBySelector(imageHTML, '#myimg')
const $ = await next.render$('/next-api/image-new')
const imageTag = $('#myimg')
expect(imageTag.attr('src')).toMatch(/test.+jpg/)
})
it('should handle various kinds of exports correctly', async () => {
const html = await renderViaHTTP(next.url, '/various-exports')
const content = getNodeBySelector(html, 'body').text()
const $ = await next.render$('/various-exports')
const content = $('body').text()
expect(content).toContain('abcde')
expect(content).toContain('default-export-arrow.client')
expect(content).toContain('named.client')
const browser = await webdriver(next.url, '/various-exports')
const browser = await next.browser('/various-exports')
const hydratedContent = await browser.waitForElementByCss('body').text()
expect(hydratedContent).toContain('abcde')
@ -287,17 +277,17 @@ describe('app dir - rsc basics', () => {
})
it('should support native modules in server component', async () => {
const html = await renderViaHTTP(next.url, '/native-module')
const content = getNodeBySelector(html, 'body').text()
const $ = await next.render$('/native-module')
const content = $('body').text()
expect(content).toContain('fs: function')
expect(content).toContain('foo.client')
})
it('should resolve different kinds of components correctly', async () => {
const html = await renderViaHTTP(next.url, '/shared')
const main = getNodeBySelector(html, '#main').html()
const content = getNodeBySelector(html, '#bar').text()
const $ = await next.render$('/shared')
const main = $('#main').html()
const content = $('#bar').text()
// Should have 5 occurrences of "client_component".
expect(Array.from(main.matchAll(/client_component/g)).length).toBe(5)
@ -317,8 +307,8 @@ describe('app dir - rsc basics', () => {
})
it('should render initial styles of css-in-js in nodejs SSR correctly', async () => {
const html = await renderViaHTTP(next.url, '/css-in-js')
const head = getNodeBySelector(html, 'head').html()
const $ = await next.render$('/css-in-js')
const head = $('head').html()
// from styled-jsx
expect(head).toMatch(/{color:(\s*)purple;?}/) // styled-jsx/style
@ -329,8 +319,8 @@ describe('app dir - rsc basics', () => {
})
it('should render initial styles of css-in-js in edge SSR correctly', async () => {
const html = await renderViaHTTP(next.url, '/css-in-js/edge')
const head = getNodeBySelector(html, 'head').html()
const $ = await next.render$('/css-in-js/edge')
const head = $('head').html()
// from styled-jsx
expect(head).toMatch(/{color:(\s*)purple;?}/) // styled-jsx/style
@ -341,28 +331,26 @@ describe('app dir - rsc basics', () => {
})
it('should render css-in-js suspense boundary correctly', async () => {
await fetchViaHTTP(next.url, '/css-in-js/suspense', null, {}).then(
async (response) => {
const results = []
await next.fetch('/css-in-js/suspense', null, {}).then(async (response) => {
const results = []
await resolveStreamResponse(response, (chunk: string) => {
// check if rsc refresh script for suspense show up, the test content could change with react version
const hasRCScript = /\$RC=function/.test(chunk)
if (hasRCScript) results.push('refresh-script')
await resolveStreamResponse(response, (chunk: string) => {
// check if rsc refresh script for suspense show up, the test content could change with react version
const hasRCScript = /\$RC=function/.test(chunk)
if (hasRCScript) results.push('refresh-script')
const isSuspenseyDataResolved =
/<style[^<>]*>(\s)*.+{padding:2px;(\s)*color:orange;}/.test(chunk)
if (isSuspenseyDataResolved) results.push('data')
const isSuspenseyDataResolved =
/<style[^<>]*>(\s)*.+{padding:2px;(\s)*color:orange;}/.test(chunk)
if (isSuspenseyDataResolved) results.push('data')
const isFallbackResolved = chunk.includes('fallback')
if (isFallbackResolved) results.push('fallback')
})
const isFallbackResolved = chunk.includes('fallback')
if (isFallbackResolved) results.push('fallback')
})
expect(results).toEqual(['fallback', 'data', 'refresh-script'])
}
)
expect(results).toEqual(['fallback', 'data', 'refresh-script'])
})
// // TODO-APP: fix streaming/suspense within browser for test suite
// const browser = await webdriver(next.url, '/css-in-js', { waitHydration: false })
// const browser = await next.browser( '/css-in-js', { waitHydration: false })
// const footer = await browser.elementByCss('#footer')
// expect(await footer.text()).toBe('wait for fallback')
// expect(
@ -390,7 +378,7 @@ describe('app dir - rsc basics', () => {
})
it('should stick to the url without trailing /page suffix', async () => {
const browser = await webdriver(next.url, '/edge/dynamic')
const browser = await next.browser('/edge/dynamic')
const indexUrl = await browser.url()
await browser.loadPage(`${next.url}/edge/dynamic/123`, {
@ -404,51 +392,50 @@ describe('app dir - rsc basics', () => {
})
it('should support streaming for flight response', async () => {
await fetchViaHTTP(
next.url,
'/',
{},
{
headers: {
['RSC'.toString()]: '1',
},
}
).then(async (response) => {
const result = await resolveStreamResponse(response)
expect(result).toContain('component:index.server')
})
await next
.fetch(
'/',
{},
{
headers: {
['RSC'.toString()]: '1',
},
}
)
.then(async (response) => {
const result = await resolveStreamResponse(response)
expect(result).toContain('component:index.server')
})
})
it('should support partial hydration with inlined server data', async () => {
await fetchViaHTTP(next.url, '/partial-hydration', null, {}).then(
async (response) => {
let gotFallback = false
let gotData = false
let gotInlinedData = false
await next.fetch('/partial-hydration', null, {}).then(async (response) => {
let gotFallback = false
let gotData = false
let gotInlinedData = false
await resolveStreamResponse(response, (_, result) => {
gotInlinedData = result.includes('self.__next_f=')
gotData = result.includes('next_streaming_data')
if (!gotFallback) {
gotFallback = result.includes('next_streaming_fallback')
if (gotFallback) {
expect(gotData).toBe(false)
expect(gotInlinedData).toBe(false)
}
await resolveStreamResponse(response, (_, result) => {
gotInlinedData = result.includes('self.__next_f=')
gotData = result.includes('next_streaming_data')
if (!gotFallback) {
gotFallback = result.includes('next_streaming_fallback')
if (gotFallback) {
expect(gotData).toBe(false)
expect(gotInlinedData).toBe(false)
}
})
}
})
expect(gotFallback).toBe(true)
expect(gotData).toBe(true)
expect(gotInlinedData).toBe(true)
}
)
expect(gotFallback).toBe(true)
expect(gotData).toBe(true)
expect(gotInlinedData).toBe(true)
})
})
// disable this flaky test
it.skip('should support partial hydration with inlined server data in browser', async () => {
// Should end up with "next_streaming_data".
const browser = await webdriver(next.url, '/partial-hydration', {
const browser = await next.browser('/partial-hydration', {
waitHydration: false,
})
const content = await browser.eval(`window.document.body.innerText`)

View file

@ -1,120 +1,98 @@
import path from 'path'
import { check, fetchViaHTTP, renderViaHTTP } from 'next-test-utils'
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { check } from 'next-test-utils'
import { createNextDescribe } from 'e2e-utils'
describe('app dir - rsc errors', () => {
let next: NextInstance
if (!(globalThis as any).isNextDev) {
it('should skip tests for next-start', () => {})
} else {
createNextDescribe(
'app dir - rsc errors',
{
files: path.join(__dirname, './rsc-errors'),
skipDeployment: true,
},
({ next }) => {
it('should throw an error when getServerSideProps is used', async () => {
const pageFile = 'app/client-with-errors/get-server-side-props/page.js'
const content = await next.readFile(pageFile)
const uncomment = content.replace(
'// export function getServerSideProps',
'export function getServerSideProps'
)
await next.patchFile(pageFile, uncomment)
const res = await next.fetch(
'/client-with-errors/get-server-side-props'
)
await next.patchFile(pageFile, content)
const { isNextDeploy, isNextDev } = global as any
if (isNextDeploy) {
it('should skip tests for next-deploy and react 17', () => {})
return
}
if (!isNextDev) {
it('should skip tests for next-start', () => {})
return
}
await check(async () => {
const { status } = await next.fetch(
'/client-with-errors/get-server-side-props'
)
return status
}, /200/)
beforeAll(async () => {
next = await createNext({
files: new FileRef(path.join(__dirname, './rsc-errors')),
dependencies: {
react: 'latest',
'react-dom': 'latest',
},
})
})
afterAll(() => next.destroy())
expect(res.status).toBe(500)
expect(await res.text()).toContain(
'"getServerSideProps\\" is not supported in app/'
)
})
it('should throw an error when getServerSideProps is used', async () => {
const pageFile = 'app/client-with-errors/get-server-side-props/page.js'
const content = await next.readFile(pageFile)
const uncomment = content.replace(
'// export function getServerSideProps',
'export function getServerSideProps'
)
await next.patchFile(pageFile, uncomment)
const res = await fetchViaHTTP(
next.url,
'/client-with-errors/get-server-side-props'
)
await next.patchFile(pageFile, content)
it('should throw an error when getStaticProps is used', async () => {
const pageFile = 'app/client-with-errors/get-static-props/page.js'
const content = await next.readFile(pageFile)
const uncomment = content.replace(
'// export function getStaticProps',
'export function getStaticProps'
)
await next.patchFile(pageFile, uncomment)
const res = await next.fetch('/client-with-errors/get-static-props')
await next.patchFile(pageFile, content)
await check(async () => {
const { status } = await next.fetch(
'/client-with-errors/get-static-props'
)
return status
}, /200/)
await check(async () => {
const { status } = await fetchViaHTTP(
next.url,
'/client-with-errors/get-server-side-props'
)
return status
}, /200/)
expect(res.status).toBe(500)
expect(await res.text()).toContain(
'"getStaticProps\\" is not supported in app/'
)
})
expect(res.status).toBe(500)
expect(await res.text()).toContain(
'"getServerSideProps\\" is not supported in app/'
)
})
it('should error for styled-jsx imports on server side', async () => {
const html = await next.render('/server-with-errors/styled-jsx')
expect(html).toContain(
'This module cannot be imported from a Server Component module. It should only be used from a Client Component.'
)
})
it('should throw an error when getStaticProps is used', async () => {
const pageFile = 'app/client-with-errors/get-static-props/page.js'
const content = await next.readFile(pageFile)
const uncomment = content.replace(
'// export function getStaticProps',
'export function getStaticProps'
)
await next.patchFile(pageFile, uncomment)
const res = await fetchViaHTTP(
next.url,
'/client-with-errors/get-static-props'
)
await next.patchFile(pageFile, content)
await check(async () => {
const { status } = await fetchViaHTTP(
next.url,
'/client-with-errors/get-static-props'
)
return status
}, /200/)
it('should error when page component export is not valid', async () => {
const html = await next.render('/server-with-errors/page-export')
expect(html).toContain(
'The default export is not a React Component in page:'
)
})
expect(res.status).toBe(500)
expect(await res.text()).toContain(
'"getStaticProps\\" is not supported in app/'
)
})
it('should throw an error when "use client" is on the top level but after other expressions', async () => {
const pageFile = 'app/swc/use-client/page.js'
const content = await next.readFile(pageFile)
const uncomment = content.replace("// 'use client'", "'use client'")
await next.patchFile(pageFile, uncomment)
const res = await next.fetch('/swc/use-client')
await next.patchFile(pageFile, content)
it('should error for styled-jsx imports on server side', async () => {
const html = await renderViaHTTP(next.url, '/server-with-errors/styled-jsx')
expect(html).toContain(
'This module cannot be imported from a Server Component module. It should only be used from a Client Component.'
)
})
await check(async () => {
const { status } = await next.fetch('/swc/use-client')
return status
}, /200/)
it('should error when page component export is not valid', async () => {
const html = await renderViaHTTP(
next.url,
'/server-with-errors/page-export'
)
expect(html).toContain(
'The default export is not a React Component in page:'
)
})
it('should throw an error when "use client" is on the top level but after other expressions', async () => {
const pageFile = 'app/swc/use-client/page.js'
const content = await next.readFile(pageFile)
const uncomment = content.replace("// 'use client'", "'use client'")
await next.patchFile(pageFile, uncomment)
const res = await fetchViaHTTP(next.url, '/swc/use-client')
await next.patchFile(pageFile, content)
await check(async () => {
const { status } = await fetchViaHTTP(next.url, '/swc/use-client')
return status
}, /200/)
expect(res.status).toBe(500)
expect(await res.text()).toContain(
'directive must be placed before other expressions'
)
})
})
expect(res.status).toBe(500)
expect(await res.text()).toContain(
'directive must be placed before other expressions'
)
})
}
)
}

View file

@ -1,25 +1,20 @@
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { createNextDescribe, FileRef } from 'e2e-utils'
import fs from 'fs-extra'
import os from 'os'
import path from 'path'
import {
fetchViaHTTP,
findPort,
initNextServerScript,
killApp,
fetchViaHTTP,
} from 'next-test-utils'
describe('output: standalone with app dir', () => {
let next: NextInstance
if (!(global as any).isNextStart) {
it('should skip for non-next start', () => {})
return
}
beforeAll(async () => {
next = await createNext({
if (!(globalThis as any).isNextStart) {
it('should skip for non-next start', () => {})
} else {
createNextDescribe(
'output: standalone with app dir',
{
files: new FileRef(path.join(__dirname, 'app')),
dependencies: {
swr: '2.0.0-rc.0',
@ -28,87 +23,88 @@ describe('output: standalone with app dir', () => {
sass: 'latest',
},
skipStart: true,
})
},
({ next }) => {
beforeAll(async () => {
await next.patchFile(
'next.config.js',
(await next.readFile('next.config.js')).replace('// output', 'output')
)
await next.start()
})
await next.patchFile(
'next.config.js',
(await next.readFile('next.config.js')).replace('// output', 'output')
)
await next.start()
})
afterAll(async () => {
await next.destroy()
})
it('should handle trace files correctly for route groups (nodejs only)', async () => {
expect(next.cliOutput).not.toContain('Failed to copy traced files')
const serverDirPath = path.join(
next.testDir,
'.next/standalone/.next/server'
)
for (const page of [
'(newroot)/dashboard/another',
'(newroot)/dashboard/project/[projectId]',
'(rootonly)/dashboard/changelog',
]) {
const pagePath = path.join(serverDirPath, 'app', page)
if ((global as any).isNextStart) {
it('should handle trace files correctly for route groups (nodejs only)', async () => {
expect(next.cliOutput).not.toContain('Failed to copy traced files')
const serverDirPath = path.join(
next.testDir,
'.next/standalone/.next/server'
)
for (const page of [
'(newroot)/dashboard/another',
'(newroot)/dashboard/project/[projectId]',
'(rootonly)/dashboard/changelog',
]) {
const pagePath = path.join(serverDirPath, 'app', page)
expect(
await fs.pathExists(path.join(pagePath, 'page.js.nft.json'))
).toBe(true)
expect(
await fs.pathExists(path.join(pagePath, 'page.js.nft.json'))
).toBe(true)
const files = (
await fs.readJSON(path.join(pagePath, 'page.js.nft.json'))
).files as string[]
const files = (
await fs.readJSON(path.join(pagePath, 'page.js.nft.json'))
).files as string[]
for (const file of files) {
expect(await fs.pathExists(path.join(pagePath, file))).toBe(true)
for (const file of files) {
expect(await fs.pathExists(path.join(pagePath, file))).toBe(true)
}
}
}
})
}
})
it('should work correctly with output standalone', async () => {
const tmpFolder = path.join(os.tmpdir(), 'next-standalone-' + Date.now())
await fs.move(path.join(next.testDir, '.next/standalone'), tmpFolder)
let server: any
it('should work correctly with output standalone', async () => {
const tmpFolder = path.join(
os.tmpdir(),
'next-standalone-' + Date.now()
)
await fs.move(path.join(next.testDir, '.next/standalone'), tmpFolder)
let server: any
try {
const testServer = path.join(tmpFolder, 'server.js')
const appPort = await findPort()
server = await initNextServerScript(
testServer,
/Listening on/,
{
...process.env,
PORT: appPort,
},
undefined,
{
cwd: tmpFolder,
try {
const testServer = path.join(tmpFolder, 'server.js')
const appPort = await findPort()
server = await initNextServerScript(
testServer,
/Listening on/,
{
...process.env,
PORT: appPort,
},
undefined,
{
cwd: tmpFolder,
}
)
for (const testPath of [
'/',
'/api/hello',
'/blog/first',
'/dashboard',
'/dashboard/another',
'/dashboard/changelog',
'/dashboard/deployments/breakdown',
'/dashboard/deployments/123',
'/dashboard/hello',
'/dashboard/project/123',
'/catch-all/first',
]) {
const res = await fetchViaHTTP(appPort, testPath)
expect(res.status).toBe(200)
}
} finally {
if (server) await killApp(server)
await fs.remove(tmpFolder)
}
)
for (const testPath of [
'/',
'/api/hello',
'/blog/first',
'/dashboard',
'/dashboard/another',
'/dashboard/changelog',
'/dashboard/deployments/breakdown',
'/dashboard/deployments/123',
'/dashboard/hello',
'/dashboard/project/123',
'/catch-all/first',
]) {
const res = await fetchViaHTTP(appPort, testPath)
expect(res.status).toBe(200)
}
} finally {
if (server) await killApp(server)
await fs.remove(tmpFolder)
})
}
})
})
)
}

View file

@ -1,62 +1,42 @@
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { fetchViaHTTP, renderViaHTTP } from 'next-test-utils'
import { createNextDescribe } from 'e2e-utils'
import path from 'path'
import cheerio from 'cheerio'
import webdriver from 'next-webdriver'
describe('app-dir trailingSlash handling', () => {
if ((global as any).isNextDeploy) {
it('should skip next deploy for now', () => {})
return
}
let next: NextInstance
beforeAll(async () => {
next = await createNext({
files: new FileRef(path.join(__dirname, 'trailingslash')),
dependencies: {
react: 'latest',
'react-dom': 'latest',
},
skipStart: true,
createNextDescribe(
'app-dir trailingSlash handling',
{
files: path.join(__dirname, 'trailingslash'),
skipDeployment: true,
},
({ next }) => {
it('should redirect route when requesting it directly', async () => {
const res = await next.fetch(
'/a',
{},
{
redirect: 'manual',
}
)
expect(res.status).toBe(308)
expect(res.headers.get('location')).toBe(next.url + '/a/')
})
await next.start()
})
afterAll(() => next.destroy())
it('should render link with trailing slash', async () => {
const $ = await next.render$('/')
expect($('#to-a-trailing-slash').attr('href')).toBe('/a/')
})
it('should redirect route when requesting it directly', async () => {
const res = await fetchViaHTTP(
next.url,
'/a',
{},
{
redirect: 'manual',
}
)
expect(res.status).toBe(308)
expect(res.headers.get('location')).toBe(next.url + '/a/')
})
it('should redirect route when requesting it directly by browser', async () => {
const browser = await next.browser('/a')
expect(await browser.waitForElementByCss('#a-page').text()).toBe('A page')
})
it('should render link with trailing slash', async () => {
const html = await renderViaHTTP(next.url, '/')
const $ = cheerio.load(html)
expect($('#to-a-trailing-slash').attr('href')).toBe('/a/')
})
it('should redirect route when requesting it directly by browser', async () => {
const browser = await webdriver(next.url, '/a')
expect(await browser.waitForElementByCss('#a-page').text()).toBe('A page')
})
it('should redirect route when clicking link', async () => {
const browser = await webdriver(next.url, '/')
await browser
.elementByCss('#to-a-trailing-slash')
.click()
.waitForElementByCss('#a-page')
expect(await browser.waitForElementByCss('#a-page').text()).toBe('A page')
})
})
it('should redirect route when clicking link', async () => {
const browser = await next.browser('/')
await browser
.elementByCss('#to-a-trailing-slash')
.click()
.waitForElementByCss('#a-page')
expect(await browser.waitForElementByCss('#a-page').text()).toBe('A page')
})
}
)

View file

@ -2,7 +2,6 @@ import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { check } from 'next-test-utils'
import path from 'path'
import webdriver from 'next-webdriver'
describe('vercel analytics', () => {
const isDev = (global as any).isNextDev
@ -48,7 +47,7 @@ describe('vercel analytics', () => {
it('should send web vitals to Vercel analytics', async () => {
let eventsCount = 0
let countEvents = false
const browser = await webdriver(next.url, '/client-nested', {
const browser = await next.browser('/client-nested', {
beforePageLoad(page) {
page.route(
'https://vitals.vercel-insights.com/v1/vitals',

View file

@ -1,31 +1,16 @@
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { renderViaHTTP } from 'next-test-utils'
import { createNextDescribe } from 'e2e-utils'
import path from 'path'
import cheerio from 'cheerio'
describe('with babel', () => {
if ((global as any).isNextDeploy) {
it('should skip next deploy for now', () => {})
return
}
let next: NextInstance
beforeAll(async () => {
next = await createNext({
files: new FileRef(path.join(__dirname, 'with-babel')),
dependencies: {
react: 'latest',
'react-dom': 'latest',
},
createNextDescribe(
'with babel',
{
files: path.join(__dirname, 'with-babel'),
skipDeployment: true,
},
({ next }) => {
it('should support babel in app dir', async () => {
const $ = await next.render$('/')
expect($('h1').text()).toBe('hello')
})
})
afterAll(() => next.destroy())
it('should support babel in app dir', async () => {
const html = await renderViaHTTP(next.url, '/')
const $ = cheerio.load(html)
expect($('h1').text()).toBe('hello')
})
})
}
)

View file

@ -35,8 +35,7 @@ describe('transpile packages', () => {
afterAll(() => next.destroy())
const { isNextDeploy } = global as any
const isReact17 = process.env.NEXT_TEST_REACT_VERSION === '^17'
if (isNextDeploy || isReact17) {
if (isNextDeploy) {
it('should skip tests for next-deploy and react 17', () => {})
return
}

View file

@ -221,7 +221,8 @@ export function createNextDescribe(
const nextProxy = new Proxy<NextInstance>({} as NextInstance, {
get: function (_target, property) {
return next[property]
const prop = next[property]
return typeof prop === 'function' ? prop.bind(next) : prop
},
})
fn({

View file

@ -364,6 +364,9 @@ export class NextInstance {
public async readFile(filename: string) {
return fs.readFile(path.join(this.testDir, filename), 'utf8')
}
public async readJSON(filename: string) {
return fs.readJSON(path.join(this.testDir, filename))
}
public async patchFile(filename: string, content: string) {
const outputPath = path.join(this.testDir, filename)
await fs.ensureDir(path.dirname(outputPath))