Skip to content

Commit ef5d806

Browse files
authored
Port "app-document" test to e2e (#77748)
<!-- Thanks for opening a PR! Your contribution is much appreciated. To make sure your PR is handled as smoothly as possible we request that you follow the checklist sections below. Choose the right checklist for the change(s) that you're making: ## For Contributors ### Improving Documentation - Run `pnpm prettier-fix` to fix formatting issues before opening the PR. - Read the Docs Contribution Guide to ensure your contribution follows the docs guidelines: https://nextjs.org/docs/community/contribution-guide ### Adding or Updating Examples - The "examples guidelines" are followed from our contributing doc https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md - Make sure the linting passes by running `pnpm build && pnpm lint`. See https://github.com/vercel/next.js/blob/canary/contributing/repository/linting.md ### Fixing a bug - Related issues linked using `fixes #number` - Tests added. See: https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs - Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md ### Adding a feature - Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. (A discussion must be opened, see https://github.com/vercel/next.js/discussions/new?category=ideas) - Related issues/discussions are linked using `fixes #number` - e2e tests added (https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) - Documentation added - Telemetry added. In case of a feature if it's used or not. - Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md ## For Maintainers - Minimal description (aim for explaining to someone not on the team to understand the PR) - When linking to a Slack thread, you might want to share details of the conclusion - Link both the Linear (Fixes NEXT-xxx) and the GitHub issues - Add review comments if necessary to explain to the reviewer the logic behind a change ### What? ### Why? ### How? Closes NEXT- Fixes # -->
1 parent 49ba0ac commit ef5d806

File tree

15 files changed

+285
-323
lines changed

15 files changed

+285
-323
lines changed

Diff for: test/e2e/app-document/client.test.ts

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { retry } from 'next-test-utils'
2+
import { nextTestSetup } from 'e2e-utils'
3+
4+
describe('Client side', () => {
5+
const { next, isNextDev } = nextTestSetup({
6+
files: __dirname,
7+
skipDeployment: true,
8+
})
9+
10+
it('should share module state with pages', async () => {
11+
const browser = await next.browser('/shared')
12+
13+
const text = await browser.elementByCss('#currentstate').text()
14+
expect(text).toBe('UPDATED CLIENT')
15+
})
16+
17+
if (isNextDev) {
18+
it('should detect the changes to pages/_app.js and display it', async () => {
19+
const appPath = 'pages/_app.js'
20+
const originalContent = await next.readFile(appPath)
21+
try {
22+
const browser = await next.browser('/')
23+
const text = await browser.elementByCss('#hello-hmr').text()
24+
expect(text).toBe('Hello HMR')
25+
26+
// change the content
27+
const editedContent = originalContent.replace('Hello HMR', 'Hi HMR')
28+
await next.patchFile(appPath, editedContent)
29+
30+
await retry(async () =>
31+
expect(await browser.elementByCss('body').text()).toContain('Hi HMR')
32+
)
33+
34+
// add the original content
35+
await next.patchFile(appPath, originalContent)
36+
37+
await retry(async () =>
38+
expect(await browser.elementByCss('body').text()).toContain(
39+
'Hello HMR'
40+
)
41+
)
42+
} finally {
43+
await next.patchFile(appPath, originalContent)
44+
}
45+
})
46+
47+
it('should detect the changes to pages/_document.js and display it', async () => {
48+
const appPath = 'pages/_document.js'
49+
const originalContent = await next.readFile(appPath)
50+
try {
51+
const browser = await next.browser('/')
52+
const text = await browser.elementByCss('#hello-hmr').text()
53+
expect(text).toBe('Hello HMR')
54+
55+
const editedContent = originalContent.replace(
56+
'Hello Document HMR',
57+
'Hi Document HMR'
58+
)
59+
60+
// change the content
61+
await next.patchFile(appPath, editedContent)
62+
63+
await retry(async () =>
64+
expect(await browser.elementByCss('body').text()).toContain(
65+
'Hi Document HMR'
66+
)
67+
)
68+
69+
// add the original content
70+
await next.patchFile(appPath, originalContent)
71+
72+
await retry(async () =>
73+
expect(await browser.elementByCss('body').text()).toContain(
74+
'Hello Document HMR'
75+
)
76+
)
77+
} finally {
78+
await next.patchFile(appPath, originalContent)
79+
}
80+
})
81+
82+
it('should keep state between page navigations', async () => {
83+
const browser = await next.browser('/')
84+
85+
const randomNumber = await browser.elementByCss('#random-number').text()
86+
87+
const switchedRandomNumer = await browser
88+
.elementByCss('#about-link')
89+
.click()
90+
.waitForElementByCss('.page-about')
91+
.elementByCss('#random-number')
92+
.text()
93+
94+
expect(switchedRandomNumer).toBe(randomNumber)
95+
await browser.close()
96+
})
97+
}
98+
})

Diff for: test/e2e/app-document/csp.test.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { nextTestSetup } from 'e2e-utils'
2+
3+
describe('With CSP enabled', () => {
4+
const { next } = nextTestSetup({
5+
files: __dirname,
6+
})
7+
8+
it('should load inline script by hash', async () => {
9+
const browser = await next.browser('/?withCSP=hash')
10+
if (global.browserName === 'chrome') {
11+
const errLog = await browser.log()
12+
expect(errLog.filter((e) => e.source === 'security')).toEqual([])
13+
}
14+
})
15+
16+
it('should load inline script by nonce', async () => {
17+
const browser = await next.browser('/?withCSP=nonce')
18+
if (global.browserName === 'chrome') {
19+
const errLog = await browser.log()
20+
expect(errLog.filter((e) => e.source === 'security')).toEqual([])
21+
}
22+
})
23+
})

Diff for: test/e2e/app-document/index.test.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { nextTestSetup } from 'e2e-utils'
2+
3+
describe('Document and App', () => {
4+
const { next } = nextTestSetup({
5+
files: __dirname,
6+
})
7+
8+
it('should not have any missing key warnings', async () => {
9+
await next.fetch('/')
10+
expect(next.cliOutput).not.toContain(
11+
'Each child in a list should have a unique "key" prop'
12+
)
13+
})
14+
})
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

Diff for: test/e2e/app-document/rendering.test.ts

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import { retry } from 'next-test-utils'
2+
import { nextTestSetup } from 'e2e-utils'
3+
4+
describe('app-document - Rendering via HTTP', () => {
5+
const { next, isNextDev } = nextTestSetup({
6+
files: __dirname,
7+
})
8+
9+
describe('_document', () => {
10+
it('should include required elements in rendered html', async () => {
11+
const $ = await next.render$('/')
12+
// It has a custom html class
13+
expect($('html').hasClass('test-html-props')).toBe(true)
14+
// It has a custom body class
15+
expect($('body').hasClass('custom_class')).toBe(true)
16+
// It injects custom head tags
17+
expect($('head').text()).toMatch('body { margin: 0 }')
18+
// It has __NEXT_DATA__ script tag
19+
expect($('script#__NEXT_DATA__')).toBeTruthy()
20+
// It passes props from Document.getInitialProps to Document
21+
expect($('#custom-property').text()).toBe('Hello Document')
22+
})
23+
24+
it('Document.getInitialProps returns html prop representing app shell', async () => {
25+
// Extract css-in-js-class from the rendered HTML, which is returned by Document.getInitialProps
26+
const $index = await next.render$('/')
27+
const $about = await next.render$('/about')
28+
expect($index('#css-in-cjs-count').text()).toBe('2')
29+
expect($about('#css-in-cjs-count').text()).toBe('0')
30+
})
31+
32+
it('adds nonces to all scripts and preload links', async () => {
33+
const $ = await next.render$('/')
34+
const nonce = 'test-nonce'
35+
let noncesAdded = true
36+
$('script, link[rel=preload]').each((index, element) => {
37+
if ($(element).attr('nonce') !== nonce) noncesAdded = false
38+
})
39+
expect(noncesAdded).toBe(true)
40+
})
41+
42+
it('adds crossOrigin to all scripts and preload links', async () => {
43+
const $ = await next.render$('/')
44+
const crossOrigin = 'anonymous'
45+
$('script, link[rel=preload]').each((index, element) => {
46+
expect($(element).attr('crossorigin') === crossOrigin).toBeTruthy()
47+
})
48+
})
49+
50+
it('renders ctx.renderPage with enhancer correctly', async () => {
51+
const $ = await next.render$('/?withEnhancer=true')
52+
const nonce = 'RENDERED'
53+
expect($('#render-page-enhance-component').text().includes(nonce)).toBe(
54+
true
55+
)
56+
})
57+
58+
it('renders ctx.renderPage with enhanceComponent correctly', async () => {
59+
const $ = await next.render$('/?withEnhanceComponent=true')
60+
const nonce = 'RENDERED'
61+
expect($('#render-page-enhance-component').text().includes(nonce)).toBe(
62+
true
63+
)
64+
})
65+
66+
it('renders ctx.renderPage with enhanceApp correctly', async () => {
67+
const $ = await next.render$('/?withEnhanceApp=true')
68+
const nonce = 'RENDERED'
69+
expect($('#render-page-enhance-app').text().includes(nonce)).toBe(true)
70+
})
71+
72+
it('renders ctx.renderPage with enhanceApp and enhanceComponent correctly', async () => {
73+
const $ = await next.render$(
74+
'/?withEnhanceComponent=true&withEnhanceApp=true'
75+
)
76+
const nonce = 'RENDERED'
77+
expect($('#render-page-enhance-app').text().includes(nonce)).toBe(true)
78+
expect($('#render-page-enhance-component').text().includes(nonce)).toBe(
79+
true
80+
)
81+
})
82+
83+
if (isNextDev) {
84+
// This is a workaround to fix https://github.com/vercel/next.js/issues/5860
85+
// TODO: remove this workaround when https://bugs.webkit.org/show_bug.cgi?id=187726 is fixed.
86+
it('adds a timestamp to link tags with preload attribute to invalidate the cache in dev', async () => {
87+
const $ = await next.render$('/', undefined, {
88+
headers: { 'user-agent': 'Safari' },
89+
})
90+
$('link[rel=preload]').each((index, element) => {
91+
const href = $(element).attr('href')
92+
expect(href.match(/\?/g)).toHaveLength(1)
93+
expect(href).toMatch(/\?ts=/)
94+
})
95+
$('script[src]').each((index, element) => {
96+
const src = $(element).attr('src')
97+
expect(src.match(/\?/g)).toHaveLength(1)
98+
expect(src).toMatch(/\?ts=/)
99+
})
100+
})
101+
}
102+
})
103+
104+
describe('_app', () => {
105+
it('shows a custom tag', async () => {
106+
const $ = await next.render$('/')
107+
expect($('#hello-app').text()).toBe('Hello App')
108+
})
109+
110+
// For example react context uses shared module state
111+
// Also known as singleton modules
112+
it('should share module state with pages', async () => {
113+
const $ = await next.render$('/shared')
114+
expect($('#currentstate').text()).toBe('UPDATED')
115+
})
116+
117+
if (isNextDev) {
118+
it('should show valid error when thrown in _app getInitialProps', async () => {
119+
const errMsg = 'have an error from _app getInitialProps'
120+
const origContent = await next.readFile('pages/_app.js')
121+
122+
expect(await next.render('/')).toContain('page-index')
123+
124+
await next.patchFile(
125+
'pages/_app.js',
126+
origContent.replace(
127+
'// throw _app GIP err here',
128+
`throw new Error("${errMsg}")`
129+
)
130+
)
131+
132+
let foundErr = false
133+
try {
134+
await retry(async () =>
135+
expect(await next.render('/')).toContain(errMsg)
136+
)
137+
foundErr = true
138+
} finally {
139+
await next.patchFile('pages/_app.js', origContent)
140+
141+
// Make sure _app is restored
142+
await retry(async () =>
143+
expect(await next.render('/')).toContain('page-index')
144+
)
145+
expect(foundErr).toBeTruthy()
146+
}
147+
})
148+
}
149+
})
150+
})

Diff for: test/integration/app-document/test/client.js

-97
This file was deleted.

0 commit comments

Comments
 (0)