Skip to content

Commit f00e379

Browse files
committed
tests
1 parent cbc4c8c commit f00e379

File tree

4 files changed

+97
-0
lines changed

4 files changed

+97
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default async function Layout({ children }) {
2+
return (
3+
<html>
4+
<body>{children}</body>
5+
</html>
6+
)
7+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// This repros a bug from https://github.com/vercel/next.js/issues/86662.
2+
// The bug from occurs if we have:
3+
// - a cache (which requires warming = a restart in dev)
4+
// - a dynamic function (blocked until the dynamic stage, like an uncached fetch)
5+
// that dedupes concurrent calls. note that that's not quite the same as caching it,
6+
// because it gets dropped after finishing.
7+
8+
import { Suspense } from 'react'
9+
10+
export default async function Page() {
11+
return (
12+
<main>
13+
<Suspense fallback={<div>Loading...</div>}>
14+
<div id="random">
15+
<DynamicRandom />
16+
</div>
17+
</Suspense>
18+
</main>
19+
)
20+
}
21+
22+
async function DynamicRandom() {
23+
const [, random] = await Promise.all([
24+
// ensure that there's a restart to warm this cache.
25+
cached(),
26+
// This fetch going to be blocked on the dynamic stage.
27+
// That promise should be rejected when restarting.
28+
// If it's not rejected (like it wasn't before the fix),
29+
// it'll remain hanging, and we'll never render anything.
30+
fetchDynamicRandomDeduped(),
31+
])
32+
return random
33+
}
34+
35+
function dedupeConcurrent<T>(func: () => Promise<T>): () => Promise<T> {
36+
let pending: Promise<T> | null = null
37+
return () => {
38+
if (pending !== null) {
39+
console.log('dedupe :: re-using pending promise')
40+
return pending
41+
}
42+
43+
console.log('dedupe :: starting')
44+
const promise = func()
45+
pending = promise
46+
47+
const clearPending = () => {
48+
console.log('dedupe :: finished')
49+
pending = null
50+
}
51+
promise.then(clearPending, clearPending)
52+
53+
return promise
54+
}
55+
}
56+
57+
const fetchDynamicRandomDeduped = dedupeConcurrent(async () => {
58+
const res = await fetch(
59+
'https://next-data-api-endpoint.vercel.app/api/random'
60+
)
61+
if (!res.ok) {
62+
throw new Error(`request failed with status ${res.status}`)
63+
}
64+
const text = await res.text()
65+
return text
66+
})
67+
68+
async function cached() {
69+
'use cache'
70+
await new Promise((resolve) => setTimeout(resolve))
71+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { nextTestSetup } from 'e2e-utils'
2+
3+
describe('cache-components-dev-warmup - reused promise', () => {
4+
const { next } = nextTestSetup({
5+
files: __dirname,
6+
})
7+
8+
it('aborts dynamic promises when restarting the render', async () => {
9+
const browser = await next.browser('/')
10+
expect(await browser.elementByCss('#random').text()).toMatch(/\d+\.\d+/)
11+
})
12+
})
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { NextConfig } from 'next'
2+
3+
const nextConfig: NextConfig = {
4+
cacheComponents: true,
5+
}
6+
7+
export default nextConfig

0 commit comments

Comments
 (0)