Skip to content

Commit 8d313be

Browse files
committed
add tests for navigations
1 parent 432e5aa commit 8d313be

File tree

4 files changed

+234
-128
lines changed

4 files changed

+234
-128
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import Link from 'next/link'
2+
3+
export default function Page() {
4+
// NOTE: these links must be kept in sync with `path` variables used in the test
5+
return (
6+
<main>
7+
<ul>
8+
<li>
9+
<Link href="/simple">/simple</Link>
10+
</li>
11+
<li>
12+
<Link href="/private-cache">/private-cache</Link>
13+
</li>
14+
<li>
15+
<Link href="/short-lived-cache">/short-lived-cache</Link>
16+
</li>
17+
<li>
18+
<Link href="/apis/123">/apis/123</Link>
19+
</li>
20+
</ul>
21+
</main>
22+
)
23+
}
Lines changed: 202 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,28 @@
11
import { nextTestSetup } from 'e2e-utils'
2+
import { retry } from 'next-test-utils'
3+
import type { Playwright } from '../../../lib/next-webdriver'
4+
import { styleText } from 'node:util'
25

36
describe('cache-components-dev-warmup', () => {
4-
const { next } = nextTestSetup({
7+
const { next, isTurbopack } = nextTestSetup({
58
files: __dirname,
69
})
710

11+
// Restart the dev server for each test to clear the in-memory cache.
12+
// We're testing cache-warming behavior here, so we don't want tests to interfere with each other.
13+
let isFirstTest = true
14+
beforeEach(async () => {
15+
if (isFirstTest) {
16+
// There's no point restarting if this is the first test.
17+
isFirstTest = false
18+
return
19+
}
20+
21+
await next.stop()
22+
await next.clean()
23+
await next.start()
24+
})
25+
826
function assertLog(
927
logs: Array<{ source: string; message: string }>,
1028
message: string,
@@ -38,145 +56,206 @@ describe('cache-components-dev-warmup', () => {
3856
])
3957
}
4058

41-
describe('logs with Prerender or Server environment depending based on whether the timing of when the log runs relative to this environment boundary', () => {
42-
it('cached data + cached fetch', async () => {
43-
const path = '/simple'
44-
const browser = await next.browser(path)
45-
46-
const assertLogs = async () => {
47-
const logs = await browser.log()
48-
assertLog(logs, 'after cache read - layout', 'Prerender')
49-
assertLog(logs, 'after cache read - page', 'Prerender')
50-
assertLog(logs, 'after successive cache reads - page', 'Prerender')
51-
assertLog(logs, 'after cached fetch - layout', 'Prerender')
52-
assertLog(logs, 'after cached fetch - page', 'Prerender')
53-
54-
assertLog(logs, 'after uncached fetch - layout', 'Server')
55-
assertLog(logs, 'after uncached fetch - page', 'Server')
56-
}
57-
58-
// Initial load.
59-
await assertLogs()
60-
61-
// After another load (with warm caches) the logs should be the same.
62-
await browser.loadPage(next.url + path) // clears old logs
63-
await assertLogs()
64-
65-
// After a revalidation the subsequent warmup render must discard stale
66-
// cache entries.
67-
// This should not affect the environment labels.
68-
await next.fetch(`/revalidate?path=${encodeURIComponent(path)}`)
59+
async function testInitialLoad(
60+
path: string,
61+
assertLogs: (browser: Playwright) => Promise<void>
62+
) {
63+
const browser = await next.browser(path)
6964

70-
await browser.loadPage(next.url + path) // clears old logs
71-
await assertLogs()
72-
})
65+
// Initial load.
66+
await retry(() => assertLogs(browser))
7367

74-
it('cached data + private cache', async () => {
75-
const path = '/private-cache'
76-
const browser = await next.browser(path)
68+
// After another load (with warm caches) the logs should be the same.
69+
await browser.loadPage(next.url + path) // clears old logs
70+
await retry(() => assertLogs(browser))
71+
72+
if (isTurbopack) {
73+
// FIXME:
74+
// In Turbopack, requests to the /revalidate route seem to occasionally crash
75+
// due to some HMR or compilation issue. `revalidatePath` throws this error:
76+
//
77+
// Invariant: static generation store missing in revalidatePath <path>
78+
//
79+
// This is unrelated to the logic being tested here, so for now, we skip the assertions
80+
// that require us to revalidate.
81+
console.log(
82+
styleText(
83+
'red',
84+
'WARNING: skipping revalidation assertions in turbopack'
85+
)
86+
)
87+
return
88+
}
7789

78-
const assertLogs = async () => {
79-
const logs = await browser.log()
80-
assertLog(logs, 'after cache read - layout', 'Prerender')
81-
assertLog(logs, 'after cache read - page', 'Prerender')
90+
// After a revalidation the subsequent warmup render must discard stale
91+
// cache entries.
92+
// This should not affect the environment labels.
93+
await revalidatePath(path)
8294

83-
// Private caches are dynamic holes in static prerenders,
84-
// so they shouldn't resolve in the static stage.
85-
assertLog(logs, 'after private cache read - page', 'Server') // TODO: 'Runtime Prerender'
86-
assertLog(logs, 'after private cache read - layout', 'Server') // TODO: 'Runtime Prerender'
87-
assertLog(logs, 'after successive private cache reads - page', 'Server') // TODO: 'Runtime Prerender'
95+
await browser.loadPage(next.url + path) // clears old logs
96+
await retry(() => assertLogs(browser))
97+
}
8898

89-
assertLog(logs, 'after uncached fetch - layout', 'Server')
90-
assertLog(logs, 'after uncached fetch - page', 'Server')
91-
}
99+
async function testNavigation(
100+
path: string,
101+
assertLogs: (browser: Playwright) => Promise<void>
102+
) {
103+
const browser = await next.browser('/')
104+
105+
// Initial nav (first time loading the page)
106+
await browser.elementByCss(`a[href="${path}"]`).click()
107+
await retry(() => assertLogs(browser))
108+
109+
// Reload, and perform another nav (with warm caches). the logs should be the same.
110+
await browser.loadPage(next.url + '/') // clears old logs
111+
await browser.elementByCss(`a[href="${path}"]`).click()
112+
await retry(() => assertLogs(browser))
113+
114+
if (isTurbopack) {
115+
// FIXME:
116+
// In Turbopack, requests to the /revalidate route seem to occasionally crash
117+
// due to some HMR or compilation issue. `revalidatePath` throws this error:
118+
//
119+
// Invariant: static generation store missing in revalidatePath <path>
120+
//
121+
// This is unrelated to the logic being tested here, so for now, we skip the assertions
122+
// that require us to revalidate.
123+
console.log(
124+
styleText(
125+
'red',
126+
'WARNING: skipping revalidation assertions in turbopack'
127+
)
128+
)
129+
return
130+
}
92131

93-
// Initial load.
94-
await assertLogs()
132+
// After a revalidation the subsequent warmup render must discard stale
133+
// cache entries.
134+
// This should not affect the environment labels.
135+
await revalidatePath(path)
95136

96-
// After another load (with warm caches) the logs should be the same.
97-
// Note that private caches are not currently persisted outside of the request that uses them.
98-
await browser.loadPage(next.url + path) // clears old logs
99-
await assertLogs()
137+
await browser.loadPage(next.url + '/') // clears old logs
138+
await browser.elementByCss(`a[href="${path}"]`).click()
139+
await retry(() => assertLogs(browser))
140+
}
100141

101-
// After a revalidation the subsequent warmup render must discard stale
102-
// cache entries.
103-
// This should not affect the environment labels.
104-
await next.fetch(`/revalidate?path=${encodeURIComponent(path)}`)
142+
async function revalidatePath(path: string) {
143+
const response = await next.fetch(
144+
`/revalidate?path=${encodeURIComponent(path)}`
145+
)
146+
if (!response.ok) {
147+
throw new Error(
148+
`Failed to revalidate path: '${path}' - server responded with status ${response.status}`
149+
)
150+
}
151+
}
105152

106-
await browser.loadPage(next.url + path) // clears old logs
107-
await assertLogs()
153+
describe.each([
154+
{ description: 'initial load', isInitialLoad: true },
155+
{ description: 'navigation', isInitialLoad: false },
156+
])('$description', ({ isInitialLoad }) => {
157+
describe('cached data resolves in the correct phase', () => {
158+
it('cached data + cached fetch', async () => {
159+
const path = '/simple'
160+
const assertLogs = async (browser: Playwright) => {
161+
const logs = await browser.log()
162+
assertLog(logs, 'after cache read - layout', 'Prerender')
163+
assertLog(logs, 'after cache read - page', 'Prerender')
164+
assertLog(logs, 'after successive cache reads - page', 'Prerender')
165+
assertLog(logs, 'after cached fetch - layout', 'Prerender')
166+
assertLog(logs, 'after cached fetch - page', 'Prerender')
167+
168+
assertLog(logs, 'after uncached fetch - layout', 'Server')
169+
assertLog(logs, 'after uncached fetch - page', 'Server')
170+
}
171+
172+
if (isInitialLoad) {
173+
await testInitialLoad(path, assertLogs)
174+
} else {
175+
await testNavigation(path, assertLogs)
176+
}
177+
})
178+
179+
it('cached data + private cache', async () => {
180+
const path = '/private-cache'
181+
182+
const assertLogs = async (browser: Playwright) => {
183+
const logs = await browser.log()
184+
assertLog(logs, 'after cache read - layout', 'Prerender')
185+
assertLog(logs, 'after cache read - page', 'Prerender')
186+
187+
// Private caches are dynamic holes in static prerenders,
188+
// so they shouldn't resolve in the static stage.
189+
assertLog(logs, 'after private cache read - page', 'Server') // TODO: 'Runtime Prerender'
190+
assertLog(logs, 'after private cache read - layout', 'Server') // TODO: 'Runtime Prerender'
191+
assertLog(
192+
logs,
193+
'after successive private cache reads - page',
194+
'Server'
195+
) // TODO: 'Runtime Prerender'
196+
197+
assertLog(logs, 'after uncached fetch - layout', 'Server')
198+
assertLog(logs, 'after uncached fetch - page', 'Server')
199+
}
200+
201+
if (isInitialLoad) {
202+
await testInitialLoad(path, assertLogs)
203+
} else {
204+
await testNavigation(path, assertLogs)
205+
}
206+
})
207+
208+
it('cached data + short-lived cached data', async () => {
209+
const path = '/short-lived-cache'
210+
211+
const assertLogs = async (browser: Playwright) => {
212+
const logs = await browser.log()
213+
assertLog(logs, 'after cache read - layout', 'Prerender')
214+
assertLog(logs, 'after cache read - page', 'Prerender')
215+
216+
// Short lived caches are dynamic holes in static prerenders,
217+
// so they shouldn't resolve in the static stage.
218+
assertLog(logs, 'after short-lived cache read - page', 'Server')
219+
assertLog(logs, 'after short-lived cache read - layout', 'Server')
220+
221+
assertLog(logs, 'after uncached fetch - layout', 'Server')
222+
assertLog(logs, 'after uncached fetch - page', 'Server')
223+
}
224+
225+
if (isInitialLoad) {
226+
await testInitialLoad(path, assertLogs)
227+
} else {
228+
await testNavigation(path, assertLogs)
229+
}
230+
})
108231
})
109232

110-
it('cached data + short-lived cached data', async () => {
111-
const path = '/short-lived-cache'
112-
const browser = await next.browser(path)
233+
it('request APIs resolve in the correct phase', async () => {
234+
const path = '/apis/123'
113235

114-
const assertLogs = async () => {
236+
const assertLogs = async (browser: Playwright) => {
115237
const logs = await browser.log()
116-
assertLog(logs, 'after cache read - layout', 'Prerender')
117238
assertLog(logs, 'after cache read - page', 'Prerender')
118239

119-
// Short lived caches are dynamic holes in static prerenders,
120-
// so they shouldn't resolve in the static stage.
121-
assertLog(logs, 'after short-lived cache read - page', 'Server')
122-
assertLog(logs, 'after short-lived cache read - layout', 'Server')
123-
124-
assertLog(logs, 'after uncached fetch - layout', 'Server')
125-
assertLog(logs, 'after uncached fetch - page', 'Server')
240+
for (const apiName of [
241+
'cookies',
242+
'headers',
243+
// TODO(restart-on-cache-miss): these two are currently broken/flaky,
244+
// because they're created outside of render and can resolve too early.
245+
// This will be fixed in a follow-up.
246+
// 'params',
247+
// 'searchParams',
248+
'connection',
249+
]) {
250+
assertLog(logs, `after ${apiName}`, 'Server')
251+
}
126252
}
127253

128-
// Initial load.
129-
await assertLogs()
130-
131-
// After another load (with warm caches) the logs should be the same.
132-
await browser.loadPage(next.url + path) // clears old logs
133-
await assertLogs()
134-
135-
// After a revalidation the subsequent warmup render must discard stale
136-
// cache entries.
137-
// This should not affect the environment labels.
138-
await next.fetch(`/revalidate?path=${encodeURIComponent(path)}`)
139-
140-
await browser.loadPage(next.url + path) // clears old logs
141-
await assertLogs()
142-
})
143-
})
144-
145-
it('runtime/dynamic APIs', async () => {
146-
const path = '/apis/123'
147-
const browser = await next.browser(path)
148-
149-
const assertLogs = async () => {
150-
const logs = await browser.log()
151-
assertLog(logs, 'after cache read - page', 'Prerender')
152-
153-
for (const apiName of [
154-
'cookies',
155-
'headers',
156-
// TODO(restart-on-cache-miss): these two are currently broken/flaky,
157-
// because they're created outside of render and can resolve too early.
158-
// This will be fixed in a follow-up.
159-
// 'params',
160-
// 'searchParams',
161-
'connection',
162-
]) {
163-
assertLog(logs, `after ${apiName}`, 'Server')
254+
if (isInitialLoad) {
255+
await testInitialLoad(path, assertLogs)
256+
} else {
257+
await testNavigation(path, assertLogs)
164258
}
165-
}
166-
167-
// Initial load.
168-
await assertLogs()
169-
170-
// After another load (with warm caches) the logs should be the same.
171-
await browser.loadPage(next.url + path) // clears old logs
172-
await assertLogs()
173-
174-
// After a revalidation the subsequent warmup render must discard stale
175-
// cache entries.
176-
// This should not affect the environment labels.
177-
await next.fetch(`/revalidate?path=${encodeURIComponent(path)}`)
178-
179-
await browser.loadPage(next.url + path) // clears old logs
180-
await assertLogs()
259+
})
181260
})
182261
})

test/development/app-dir/cache-components-dev-warmup/next.config.js

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { NextConfig } from 'next'
2+
3+
const nextConfig: NextConfig = {
4+
experimental: {
5+
cacheComponents: true,
6+
},
7+
}
8+
9+
export default nextConfig

0 commit comments

Comments
 (0)