Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions packages/plugin-rsc/e2e/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,73 @@ function defineTest(f: Fixture) {
)
})

test('React.cache API availability', async ({ page }) => {
await page.goto(f.url())
await waitForHydration(page)

// Test that the React.cache component renders
await expect(page.getByTestId('react-cache-test')).toContainText(
'React.cache Test',
)

// Test React.cache and React.use are available
await expect(page.getByTestId('react-cache-available')).toContainText(
'React.cache available: Yes',
)
await expect(page.getByTestId('react-use-available')).toContainText(
'React.use available: Yes',
)

// Test that we can create cached functions
await expect(page.getByTestId('api-test-result')).toContainText(
'Success - created cached function of type: function',
)
})

test('React.cache synchronous behavior', async ({ page }) => {
await page.goto(f.url())
await waitForHydration(page)

// Test synchronous cache behavior
const result1 = await page.getByTestId('sync-result1').textContent()
const result2 = await page.getByTestId('sync-result2').textContent()

// Verify both calls return the same value (indicating caching)
expect(result1).toBe(result2)
await expect(page.getByTestId('sync-results-equal')).toContainText(
'Results equal: true',
)
})

test('React.cache with async operations', async ({ page }) => {
await page.goto(f.url())
await waitForHydration(page)

// Wait for suspense to resolve
await expect(page.getByTestId('async-result')).toBeVisible()

// Verify async cache result loads
await expect(page.getByTestId('async-result')).toContainText('Async result')
})

test('React.cache re-render behavior', async ({ page }) => {
await page.goto(f.url())
await waitForHydration(page)

// Get initial call count
const initialCallCount = await page
.getByTestId('sync-call-count')
.textContent()

// Force a re-render
await page.getByTestId('cache-test-rerender').click()

// Verify call count (behavior may vary between React versions)
const newCallCount = await page.getByTestId('sync-call-count').textContent()
expect(newCallCount).toBeTruthy()
expect(initialCallCount).toBeTruthy()
})

test('hydration mismatch', async ({ page }) => {
const errors: Error[] = []
page.on('pageerror', (error) => {
Expand Down
127 changes: 127 additions & 0 deletions packages/plugin-rsc/examples/basic/src/routes/react-cache/client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
'use client'

import React, { useState } from 'react'

let callCount = 0

// Create a cached function that should demonstrate React.cache behavior
const cacheFn = React.cache(() => {
callCount++
return `Called ${callCount} times`
})

// Create an async cached function for demonstration
const asyncCacheFn = React.cache(async () => {
await new Promise((r) => setTimeout(r, 10))
return `Async result`
})

export function ReactCacheTest() {
const [renderKey, setRenderKey] = useState(0)

return (
<div
className="react-cache-test"
style={{ padding: '20px', border: '1px solid #ccc', margin: '10px' }}
data-testid="react-cache-test"
>
<h3>React.cache Test</h3>
<div data-testid="react-version">React version: {React.version}</div>
<div data-testid="react-cache-available">
React.cache available:{' '}
{typeof React.cache === 'function' ? 'Yes' : 'No'}
</div>
<div data-testid="react-use-available">
React.use available: {typeof React.use === 'function' ? 'Yes' : 'No'}
</div>

<button
data-testid="cache-test-rerender"
onClick={() => setRenderKey((k) => k + 1)}
style={{ padding: '8px 16px', margin: '10px 0' }}
>
Force Re-render (count: {renderKey})
</button>

<button
data-testid="cache-test-reset"
onClick={() => {
callCount = 0
setRenderKey((k) => k + 1)
}}
style={{ padding: '8px 16px', margin: '10px 0' }}
>
Reset Call Count
</button>

{/* Test API availability */}
<ApiAvailabilityTest />

{/* Test synchronous cache behavior */}
<SyncCacheTest />

{/* Test async cache with error boundary */}
<React.Suspense
fallback={<div data-testid="cache-loading">Loading async test...</div>}
>
<AsyncCacheTest />
</React.Suspense>
</div>
)
}

function ApiAvailabilityTest() {
let result = 'unknown'
try {
const testCache = React.cache(() => 'test')
result = `Success - created cached function of type: ${typeof testCache}`
} catch (error) {
result = `Error: ${error instanceof Error ? error.message : String(error)}`
}

return (
<div
data-testid="api-test"
style={{ padding: '10px', background: '#f0f0f0', margin: '10px 0' }}
>
<h4>API Availability Test</h4>
<div data-testid="api-test-result">Cache creation: {result}</div>
</div>
)
}

function SyncCacheTest() {
// Call the cached function multiple times within the same render
const result1 = cacheFn()
const result2 = cacheFn()

return (
<div
data-testid="sync-test"
style={{ padding: '10px', background: '#e8f4f8', margin: '10px 0' }}
>
<h4>Synchronous Cache Test</h4>
<div data-testid="sync-result1">First call: {result1}</div>
<div data-testid="sync-result2">Second call: {result2}</div>
<div data-testid="sync-results-equal">
Results equal: {result1 === result2 ? 'true' : 'false'}
</div>
<div data-testid="sync-call-count">Total function calls: {callCount}</div>
</div>
)
}

function AsyncCacheTest() {
// Use React.use to consume async cached function - don't use try/catch
const asyncResult = React.use(asyncCacheFn())

return (
<div
data-testid="async-test"
style={{ padding: '10px', background: '#f0f8e8', margin: '10px 0' }}
>
<h4>Async Cache Test</h4>
<div data-testid="async-result">Async result: {asyncResult}</div>
</div>
)
}
2 changes: 2 additions & 0 deletions packages/plugin-rsc/examples/basic/src/routes/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { TestHydrationMismatch } from './hydration-mismatch/server'
import { TestBrowserOnly } from './browser-only/client'
import { TestTransitiveCjsClient } from './deps/transitive-cjs/client'
import TestDepCssInServer from '@vitejs/test-dep-css-in-server/server'
import { ReactCacheTest } from './react-cache/client'

export function Root(props: { url: URL }) {
return (
Expand Down Expand Up @@ -75,6 +76,7 @@ export function Root(props: { url: URL }) {
<TestModuleInvalidationServer />
<TestBrowserOnly />
<TestUseCache />
<ReactCacheTest />
</body>
</html>
)
Expand Down