Skip to content

Commit db8d6de

Browse files
committed
test(nextjs): add E2E tests for cache components support
- Add currentUser() server component test page and tests - Add currentUser() with "use cache" correct pattern page - Add error trigger page that calls auth() inside "use cache" - Add test verifying Clerk's custom error message is shown - Add signed-out state tests for cache and PPR pages - Update home page navigation with new test links
1 parent eed6aeb commit db8d6de

6 files changed

Lines changed: 342 additions & 4 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { auth } from '@clerk/nextjs/server';
2+
3+
// This function deliberately calls auth() inside "use cache" to trigger the error
4+
async function getCachedAuthData() {
5+
'use cache';
6+
// This WILL throw an error because auth() uses headers() internally
7+
const { userId } = await auth();
8+
return { userId };
9+
}
10+
11+
export async function GET() {
12+
try {
13+
const data = await getCachedAuthData();
14+
return Response.json(data);
15+
} catch (e: any) {
16+
// Return the error message so we can verify it in tests
17+
return Response.json({ error: e.message }, { status: 500 });
18+
}
19+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { Suspense } from 'react';
2+
import { currentUser, clerkClient } from '@clerk/nextjs/server';
3+
4+
// Simulated cached operation that fetches additional user data
5+
async function getCachedUserProfile(userId: string) {
6+
'use cache';
7+
// This is the CORRECT pattern:
8+
// - currentUser() is called OUTSIDE the cache function
9+
// - Only the userId is passed into the cache function
10+
// - The cache function uses clerkClient() which is allowed in cache contexts
11+
const client = await clerkClient();
12+
const user = await client.users.getUser(userId);
13+
14+
return {
15+
userId,
16+
cachedAt: new Date().toISOString(),
17+
profile: {
18+
fullName: [user.firstName, user.lastName].filter(Boolean).join(' ') || 'Unknown',
19+
emailCount: user.emailAddresses?.length ?? 0,
20+
},
21+
};
22+
}
23+
24+
async function CurrentUserCacheContent() {
25+
// Step 1: Call currentUser() OUTSIDE the cache function
26+
const user = await currentUser();
27+
28+
if (!user) {
29+
return (
30+
<>
31+
<p>Please sign in to test the caching pattern with currentUser().</p>
32+
<div data-testid='signed-out'>Not signed in</div>
33+
</>
34+
);
35+
}
36+
37+
// Step 2: Pass userId INTO the cache function
38+
const cachedProfile = await getCachedUserProfile(user.id);
39+
40+
return (
41+
<>
42+
<p>
43+
This demonstrates the correct way to use <code>&quot;use cache&quot;</code> with currentUser():
44+
</p>
45+
<ol>
46+
<li>
47+
Call <code>currentUser()</code> <strong>outside</strong> the cache function
48+
</li>
49+
<li>
50+
Pass the <code>userId</code> <strong>into</strong> the cache function
51+
</li>
52+
<li>
53+
Use <code>clerkClient()</code> inside the cache function (allowed)
54+
</li>
55+
</ol>
56+
57+
<div className='test-result success'>
58+
<h3>Cached Profile Data:</h3>
59+
<pre data-testid='cached-profile'>{JSON.stringify(cachedProfile, null, 2)}</pre>
60+
</div>
61+
62+
<div data-testid='current-user-id'>{user.id}</div>
63+
64+
<pre>
65+
{`
66+
// Correct pattern:
67+
const user = await currentUser(); // Outside cache
68+
if (user) {
69+
const profile = await getCachedProfile(user.id); // Pass userId in
70+
}
71+
72+
async function getCachedProfile(userId: string) {
73+
'use cache';
74+
const client = await clerkClient();
75+
return client.users.getUser(userId);
76+
}
77+
`}
78+
</pre>
79+
</>
80+
);
81+
}
82+
83+
export default function CurrentUserCacheCorrectPage() {
84+
return (
85+
<main>
86+
<h1>currentUser() with &quot;use cache&quot; Correct Pattern</h1>
87+
88+
<Suspense fallback={<div>Loading...</div>}>
89+
<CurrentUserCacheContent />
90+
</Suspense>
91+
</main>
92+
);
93+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Suspense } from 'react';
2+
import { currentUser } from '@clerk/nextjs/server';
3+
4+
async function CurrentUserContent() {
5+
const user = await currentUser();
6+
7+
return (
8+
<>
9+
<div className={`test-result ${user ? 'success' : ''}`}>
10+
<h3>Current User Result:</h3>
11+
<pre>
12+
{JSON.stringify(
13+
{
14+
id: user?.id ?? null,
15+
firstName: user?.firstName ?? null,
16+
lastName: user?.lastName ?? null,
17+
primaryEmailAddress: user?.primaryEmailAddress?.emailAddress ?? null,
18+
isSignedIn: !!user,
19+
},
20+
null,
21+
2,
22+
)}
23+
</pre>
24+
</div>
25+
26+
<div data-testid='current-user-id'>{user?.id ?? 'Not signed in'}</div>
27+
<div data-testid='current-user-email'>{user?.primaryEmailAddress?.emailAddress ?? 'No email'}</div>
28+
</>
29+
);
30+
}
31+
32+
export default function CurrentUserServerComponentPage() {
33+
return (
34+
<main>
35+
<h1>currentUser() in Server Component</h1>
36+
<p>This page tests using currentUser() in a standard React Server Component.</p>
37+
38+
<Suspense fallback={<div>Loading user...</div>}>
39+
<CurrentUserContent />
40+
</Suspense>
41+
</main>
42+
);
43+
}

integration/templates/next-cache-components/src/app/page.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,26 @@ export default function Home() {
1212
<li>
1313
<Link href='/auth-server-component'>auth() in Server Component</Link>
1414
</li>
15+
<li>
16+
<Link href='/current-user-server-component'>currentUser() in Server Component</Link>
17+
</li>
1518
<li>
1619
<Link href='/auth-server-action'>auth() in Server Action</Link>
1720
</li>
1821
<li>
1922
<Link href='/api/auth-check'>auth() in API Route</Link>
2023
</li>
2124
<li>
22-
<Link href='/use-cache-error'>use cache with auth() (should error)</Link>
25+
<Link href='/use-cache-error'>use cache with auth() (documentation)</Link>
26+
</li>
27+
<li>
28+
<Link href='/use-cache-error-trigger'>use cache error trigger (actual error)</Link>
29+
</li>
30+
<li>
31+
<Link href='/use-cache-correct'>&quot;use cache&quot; correct pattern (auth)</Link>
2332
</li>
2433
<li>
25-
<Link href='/use-cache-correct'>&quot;use cache&quot; correct pattern</Link>
34+
<Link href='/current-user-cache-correct'>&quot;use cache&quot; correct pattern (currentUser)</Link>
2635
</li>
2736
<li>
2837
<Link href='/ppr-auth'>PPR with auth()</Link>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
'use client';
2+
3+
import { useEffect, useState } from 'react';
4+
5+
export default function UseCacheErrorTriggerPage() {
6+
const [error, setError] = useState<string | null>(null);
7+
const [loading, setLoading] = useState(false);
8+
9+
const triggerError = async () => {
10+
setLoading(true);
11+
setError(null);
12+
try {
13+
const response = await fetch('/api/use-cache-error-trigger');
14+
const data = await response.json();
15+
if (data.error) {
16+
setError(data.error);
17+
}
18+
} catch (e: any) {
19+
setError(e.message);
20+
} finally {
21+
setLoading(false);
22+
}
23+
};
24+
25+
useEffect(() => {
26+
triggerError();
27+
}, []);
28+
29+
return (
30+
<main>
31+
<h1>&quot;use cache&quot; Error Trigger</h1>
32+
<p>This page triggers an actual error by calling auth() inside a &quot;use cache&quot; function.</p>
33+
34+
{loading && <div data-testid='loading'>Loading...</div>}
35+
36+
{error && (
37+
<div
38+
className='test-result error'
39+
data-testid='error-message'
40+
>
41+
<h3>Error Caught:</h3>
42+
<pre style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>{error}</pre>
43+
</div>
44+
)}
45+
46+
<button
47+
onClick={triggerError}
48+
data-testid='trigger-btn'
49+
>
50+
Trigger Error Again
51+
</button>
52+
</main>
53+
);
54+
}

0 commit comments

Comments
 (0)