Skip to content

Commit e04afcc

Browse files
committed
wip
1 parent d1bc6a6 commit e04afcc

File tree

12 files changed

+217
-3
lines changed

12 files changed

+217
-3
lines changed

Suspense-tests-todo.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Suspense Tests To-Do
2+
3+
- [x] test/use-swr-infinite.test.tsx — should update the getKey reference with the suspense mode (migrated to e2e/test/suspense-infinite-get-key.test.ts)
4+
- [x] test/use-swr-suspense.test.tsx — should render fallback (migrated to e2e/test/suspense-render-fallback.test.ts)
5+
- [x] test/use-swr-suspense.test.tsx — should render multiple SWR fallbacks (migrated to e2e/test/render-suspense-multiple-fallbacks.test.ts)
6+
- [ ] test/use-swr-suspense.test.tsx — should work for non-promises
7+
- [ ] test/use-swr-suspense.test.tsx — should throw errors
8+
- [ ] test/use-swr-suspense.test.tsx — should render cached data with error
9+
- [ ] test/use-swr-suspense.test.tsx — should not fetch when cached data is present and `revalidateIfStale` is false
10+
- [ ] test/use-swr-suspense.test.tsx — should pause when key changes
11+
- [ ] test/use-swr-suspense.test.tsx — should render correctly when key changes (but with same response data)
12+
- [ ] test/use-swr-suspense.test.tsx — should render correctly when key changes (from null to valid key)
13+
- [ ] test/use-swr-suspense.test.tsx — should render initial data if set
14+
- [ ] test/use-swr-suspense.test.tsx — should avoid unnecessary re-renders
15+
- [ ] test/use-swr-suspense.test.tsx — should return `undefined` data for falsy key
16+
- [ ] test/use-swr-suspense.test.tsx — should only render fallback once when `keepPreviousData` is set to true
17+
- [ ] test/use-swr-streaming-ssr.test.tsx — should match the ssr result when streaming and partially hydrating (failing)
18+
- [ ] test/use-swr-fetcher.test.tsx — should use the latest fetcher reference with the suspense mode when the key has been changed
19+
- [ ] test/use-swr-infinite-preload.test.tsx — preload the fetcher function with the suspense mode
20+
- [ ] test/use-swr-infinite-preload.test.tsx — avoid suspense waterfall by prefetching the resources (skipped)
21+
- [ ] test/use-swr-server.test.tsx — should enable the IS_SERVER flag - suspense on server without fallback
22+
- [ ] test/use-swr-promise.test.tsx — should suspend when resolving the fallback promise
23+
- [ ] test/use-swr-promise.test.tsx — should handle errors with fallback promises
24+
- [ ] test/use-swr-promise.test.tsx — should handle same fallback promise that is already pending
25+
- [ ] test/use-swr-preload.test.tsx — preload the fetcher function with the suspense mode
26+
- [ ] test/use-swr-preload.test.tsx — avoid suspense waterfall by prefetching the resources
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use client'
2+
3+
import { Suspense } from 'react'
4+
import useSWR from 'swr'
5+
import { OnlyRenderInClient } from '~/component/only-render-in-client'
6+
7+
function delay(ms: number) {
8+
return new Promise(resolve => setTimeout(resolve, ms))
9+
}
10+
11+
async function fetchGreeting() {
12+
await delay(500)
13+
return 'SWR'
14+
}
15+
16+
function Section() {
17+
const { data } = useSWR<string>('render-suspense-fallback', fetchGreeting, {
18+
suspense: true
19+
})
20+
21+
return <div data-testid="data">{data}</div>
22+
}
23+
24+
export default function Page() {
25+
return (
26+
<OnlyRenderInClient>
27+
<Suspense fallback={<div data-testid="fallback">fallback</div>}>
28+
<Section />
29+
</Suspense>
30+
</OnlyRenderInClient>
31+
)
32+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
'use client'
2+
3+
import { Suspense } from 'react'
4+
import useSWR from 'swr'
5+
import { OnlyRenderInClient } from '~/component/only-render-in-client'
6+
7+
function delay(ms: number) {
8+
return new Promise(resolve => setTimeout(resolve, ms))
9+
}
10+
11+
async function fetchValue(key: string) {
12+
if (key === 'render-suspense-multiple-fallbacks-1') {
13+
await delay(50)
14+
return 1
15+
}
16+
await delay(140)
17+
return 2
18+
}
19+
20+
function Section() {
21+
const { data: v1 } = useSWR<number>(
22+
'render-suspense-multiple-fallbacks-1',
23+
fetchValue,
24+
{ suspense: true }
25+
)
26+
const { data: v2 } = useSWR<number>(
27+
'render-suspense-multiple-fallbacks-2',
28+
fetchValue,
29+
{ suspense: true }
30+
)
31+
32+
return <div data-testid="data">{(v1 ?? 0) + (v2 ?? 0)}</div>
33+
}
34+
35+
export default function Page() {
36+
return (
37+
<OnlyRenderInClient>
38+
<Suspense fallback={<div data-testid="fallback">fallback</div>}>
39+
<Section />
40+
</Suspense>
41+
</OnlyRenderInClient>
42+
)
43+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
'use client'
2+
3+
import { Suspense, useState } from 'react'
4+
import useSWRInfinite from 'swr/infinite'
5+
import { OnlyRenderInClient } from '~/component/only-render-in-client'
6+
7+
const DATA: Record<string, string[]> = {
8+
'suspense-key-a': ['A1', 'A2', 'A3'],
9+
'suspense-key-b': ['B1', 'B2', 'B3']
10+
}
11+
12+
async function fetchList(key: string) {
13+
await new Promise(resolve => setTimeout(resolve, 150))
14+
return DATA[key]
15+
}
16+
17+
function List() {
18+
const [status, setStatus] = useState<'a' | 'b'>('a')
19+
const { data, setSize } = useSWRInfinite(
20+
() => (status === 'a' ? 'suspense-key-a' : 'suspense-key-b'),
21+
fetchList,
22+
{ suspense: true }
23+
)
24+
25+
return (
26+
<>
27+
<div data-testid="data">data: {String(data)}</div>
28+
<button
29+
type="button"
30+
onClick={() => {
31+
setStatus('b')
32+
void setSize(1)
33+
}}
34+
>
35+
mutate
36+
</button>
37+
</>
38+
)
39+
}
40+
41+
export default function Page() {
42+
return (
43+
<OnlyRenderInClient>
44+
<Suspense fallback={<div>loading</div>}>
45+
<List />
46+
</Suspense>
47+
</OnlyRenderInClient>
48+
)
49+
}

e2e/site/app/suspense-undefined-key/page.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { Suspense, useReducer } from 'react'
44
import useSWR from 'swr'
5+
import { OnlyRenderInClient } from '~/component/only-render-in-client'
56

67
const fetcher = async (key: string) => {
78
// Add a small delay to simulate network request
@@ -20,11 +21,11 @@ export default function Page() {
2021
const [trigger, toggle] = useReducer(x => !x, false)
2122

2223
return (
23-
<div>
24+
<OnlyRenderInClient>
2425
<button onClick={toggle}>toggle</button>
2526
<Suspense fallback={<div>fallback</div>}>
2627
<Section trigger={trigger} />
2728
</Suspense>
28-
</div>
29+
</OnlyRenderInClient>
2930
)
3031
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use client'
2+
import React from 'react'
3+
4+
// This component ensures its children are only rendered on the client side.
5+
export function OnlyRenderInClient({
6+
children
7+
}: {
8+
children?: React.ReactNode
9+
}) {
10+
const [isClient, setIsClient] = React.useState(false)
11+
React.useEffect(() => {
12+
setIsClient(true)
13+
}, [])
14+
if (!isClient) {
15+
return null
16+
}
17+
return <>{children}</>
18+
}

e2e/site/next-env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
33
/// <reference types="next/navigation-types/compat/navigation" />
4+
/// <reference path="./.next/types/routes.d.ts" />
45

56
// NOTE: This file should not be edited
67
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { expect, test } from '@playwright/test'
2+
3+
test.describe('render-suspense-fallback', () => {
4+
test('shows fallback before rendering resolved data', async ({ page }) => {
5+
await page.goto('./render-suspense-fallback', { waitUntil: 'commit' })
6+
7+
await expect(page.getByTestId('fallback')).toBeVisible()
8+
await expect(page.getByTestId('data')).toHaveText('SWR')
9+
})
10+
})
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { expect, test } from '@playwright/test'
2+
3+
test.describe('render-suspense-multiple-fallbacks', () => {
4+
test('keeps showing fallback while multiple resources resolve', async ({
5+
page
6+
}) => {
7+
await page.goto('./render-suspense-multiple-fallbacks', {
8+
waitUntil: 'commit'
9+
})
10+
11+
const fallback = page.getByTestId('fallback')
12+
await expect(fallback).toBeVisible()
13+
14+
await page.waitForTimeout(80)
15+
await expect(fallback).toBeVisible()
16+
17+
await page.waitForTimeout(120)
18+
await expect(page.getByTestId('data')).toHaveText('3')
19+
})
20+
})
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { expect, test } from '@playwright/test'
2+
3+
test.describe('suspense infinite get key', () => {
4+
test('updates the data when key changes under suspense', async ({ page }) => {
5+
await page.goto('./suspense-infinite/get-key', { waitUntil: 'commit' })
6+
7+
await expect(page.getByTestId('data')).toHaveText('data: A1,A2,A3')
8+
9+
await page.getByRole('button', { name: 'mutate' }).click()
10+
11+
await expect(page.getByTestId('data')).toHaveText('data: B1,B2,B3')
12+
})
13+
})

0 commit comments

Comments
 (0)