Skip to content

Commit 775ac61

Browse files
hi-ogawaclaude
andauthored
test(rsc): test shared module hmr (#671)
Co-authored-by: Claude <[email protected]>
1 parent 4461638 commit 775ac61

File tree

11 files changed

+199
-1
lines changed

11 files changed

+199
-1
lines changed

packages/plugin-rsc/e2e/basic.test.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,90 @@ function defineTest(f: Fixture) {
316316
editor.reset()
317317
await expect(locator).toContainText('[dep: 1]')
318318
})
319+
320+
test('shared hmr basic', async ({ page }) => {
321+
await page.goto(f.url())
322+
await waitForHydration(page)
323+
await using _ = await expectNoReload(page)
324+
325+
// Test initial state
326+
await expect(page.getByTestId('test-hmr-shared-server')).toContainText(
327+
'(shared1, shared2)',
328+
)
329+
await expect(page.getByTestId('test-hmr-shared-client')).toContainText(
330+
'(shared1, shared2)',
331+
)
332+
333+
// Test 1: Component HMR (shared1.tsx)
334+
const editor1 = f.createEditor('src/routes/hmr-shared/shared1.tsx')
335+
editor1.edit((s) => s.replace('shared1', 'shared1-edit'))
336+
337+
// Verify both server and client components updated
338+
await expect(page.getByTestId('test-hmr-shared-server')).toContainText(
339+
'(shared1-edit, shared2)',
340+
)
341+
await expect(page.getByTestId('test-hmr-shared-client')).toContainText(
342+
'(shared1-edit, shared2)',
343+
)
344+
345+
editor1.reset()
346+
await expect(page.getByTestId('test-hmr-shared-server')).toContainText(
347+
'(shared1, shared2)',
348+
)
349+
await expect(page.getByTestId('test-hmr-shared-client')).toContainText(
350+
'(shared1, shared2)',
351+
)
352+
353+
// Test 2: Non-component HMR (shared2.tsx)
354+
const editor2 = f.createEditor('src/routes/hmr-shared/shared2.tsx')
355+
editor2.edit((s) => s.replace('shared2', 'shared2-edit'))
356+
357+
// Verify both server and client components updated
358+
await expect(page.getByTestId('test-hmr-shared-server')).toContainText(
359+
'(shared1, shared2-edit)',
360+
)
361+
await expect(page.getByTestId('test-hmr-shared-client')).toContainText(
362+
'(shared1, shared2-edit)',
363+
)
364+
365+
editor2.reset()
366+
await expect(page.getByTestId('test-hmr-shared-server')).toContainText(
367+
'(shared1, shared2)',
368+
)
369+
await expect(page.getByTestId('test-hmr-shared-client')).toContainText(
370+
'(shared1, shared2)',
371+
)
372+
})
373+
374+
// for this use case to work, server refetch/render and client hmr needs to applied atomically
375+
// at the same time. Next.js doesn't seem to support this either.
376+
// https://github.com/hi-ogawa/reproductions/tree/main/next-rsc-hmr-shared-module
377+
test('shared hmr not atomic', async ({ page }) => {
378+
await page.goto(f.url())
379+
await waitForHydration(page)
380+
await expect(page.getByTestId('test-hmr-shared-atomic')).toContainText(
381+
'ok (test-shared)',
382+
)
383+
384+
// non-atomic update causes an error
385+
const editor = f.createEditor('src/routes/hmr-shared/atomic/shared.tsx')
386+
editor.edit((s) => s.replace('test-shared', 'test-shared-edit'))
387+
await expect(page.getByTestId('test-hmr-shared-atomic')).toContainText(
388+
'ErrorBoundary',
389+
)
390+
391+
await page.reload()
392+
await expect(page.getByText('ok (test-shared-edit)')).toBeVisible()
393+
394+
// non-atomic update causes an error
395+
editor.reset()
396+
await expect(page.getByTestId('test-hmr-shared-atomic')).toContainText(
397+
'ErrorBoundary',
398+
)
399+
400+
await page.reload()
401+
await expect(page.getByText('ok (test-shared)')).toBeVisible()
402+
})
319403
})
320404

321405
test('css @js', async ({ page }) => {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use client'
2+
3+
import React from 'react'
4+
import { testShared } from './shared'
5+
6+
export function TestClient({
7+
testSharedFromServer,
8+
}: {
9+
testSharedFromServer: string
10+
}) {
11+
React.useEffect(() => {
12+
console.log({ testShared, testSharedFromServer })
13+
if (testShared !== testSharedFromServer) {
14+
throw new Error(
15+
`Mismatch: ${JSON.stringify({ testShared, testSharedFromServer })}`,
16+
)
17+
}
18+
}, [testShared, testSharedFromServer])
19+
20+
return <>ok ({testShared})</>
21+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'use client'
2+
3+
import * as React from 'react'
4+
5+
interface Props {
6+
children?: React.ReactNode
7+
}
8+
9+
interface State {
10+
error: Error | null
11+
}
12+
13+
export default class ErrorBoundary extends React.Component<Props, State> {
14+
constructor(props: Props) {
15+
super(props)
16+
this.state = { error: null }
17+
}
18+
19+
static getDerivedStateFromError(error: Error) {
20+
return { error }
21+
}
22+
23+
render() {
24+
if (this.state.error) {
25+
return (
26+
<span>
27+
ErrorBoundary: {this.state.error.message}
28+
<button
29+
onClick={() => {
30+
this.setState({ error: null })
31+
}}
32+
>
33+
Reset
34+
</button>
35+
</span>
36+
)
37+
}
38+
return this.props.children
39+
}
40+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { TestClient } from './client'
2+
import ErrorBoundary from './error-boundary'
3+
import { testShared } from './shared'
4+
5+
export function TestHmrSharedAtomic() {
6+
return (
7+
<div data-testid="test-hmr-shared-atomic">
8+
test-hmr-shared-atomic:{' '}
9+
<ErrorBoundary>
10+
<TestClient testSharedFromServer={testShared} />
11+
</ErrorBoundary>
12+
</div>
13+
)
14+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const testShared = 'test-shared'
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use client'
2+
3+
import { TestHmrSharedComponent } from './shared1'
4+
import { testHmrSharedObject } from './shared2'
5+
6+
export function TestHmrSharedClient() {
7+
return (
8+
<div data-testid="test-hmr-shared-client">
9+
test-hmr-shared-client: (<TestHmrSharedComponent />,{' '}
10+
{testHmrSharedObject.value})
11+
</div>
12+
)
13+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { TestHmrSharedComponent } from './shared1'
2+
import { testHmrSharedObject } from './shared2'
3+
4+
export function TestHmrSharedServer() {
5+
return (
6+
<div data-testid="test-hmr-shared-server">
7+
test-hmr-shared-server: (<TestHmrSharedComponent />,{' '}
8+
{testHmrSharedObject.value})
9+
</div>
10+
)
11+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function TestHmrSharedComponent() {
2+
return <>shared1</>
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const testHmrSharedObject = {
2+
value: 'shared2',
3+
}

packages/plugin-rsc/examples/basic/src/routes/root.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ import { TestHydrationMismatch } from './hydration-mismatch/server'
3232
import { TestBrowserOnly } from './browser-only/client'
3333
import { TestTransitiveCjsClient } from './deps/transitive-cjs/client'
3434
import TestDepCssInServer from '@vitejs/test-dep-css-in-server/server'
35+
import { TestHmrSharedServer } from './hmr-shared/server'
36+
import { TestHmrSharedClient } from './hmr-shared/client'
37+
import { TestHmrSharedAtomic } from './hmr-shared/atomic/server'
3538

3639
export function Root(props: { url: URL }) {
3740
return (
@@ -56,6 +59,9 @@ export function Root(props: { url: URL }) {
5659
<TestDepCssInServer />
5760
<TestHydrationMismatch url={props.url} />
5861
<TestHmrClientDep />
62+
<TestHmrSharedServer />
63+
<TestHmrSharedClient />
64+
<TestHmrSharedAtomic />
5965
<TestTemporaryReference />
6066
<TestServerActionError />
6167
<TestReplayConsoleLogs url={props.url} />

0 commit comments

Comments
 (0)