-
-
Notifications
You must be signed in to change notification settings - Fork 9.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Build: Exclude server action stories to run in next 13 #29592
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import React from 'react'; | ||
|
||
import { revalidatePath } from '@storybook/nextjs/cache.mock'; | ||
import { cookies } from '@storybook/nextjs/headers.mock'; | ||
import { getRouter, redirect } from '@storybook/nextjs/navigation.mock'; | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import { expect, userEvent, waitFor, within } from '@storybook/test'; | ||
|
||
import { accessRoute, login, logout } from './server-actions'; | ||
|
||
function Component() { | ||
return ( | ||
<div style={{ display: 'flex', gap: 8 }}> | ||
<form> | ||
<button type="submit" formAction={login}> | ||
Login | ||
</button> | ||
</form> | ||
<form> | ||
<button type="submit" formAction={logout}> | ||
Logout | ||
</button> | ||
</form> | ||
<form> | ||
<button type="submit" formAction={accessRoute}> | ||
Access protected route | ||
</button> | ||
</form> | ||
</div> | ||
); | ||
} | ||
|
||
export default { | ||
component: Component, | ||
tags: ['!test'], | ||
parameters: { | ||
nextjs: { | ||
appDirectory: true, | ||
navigation: { | ||
pathname: '/', | ||
}, | ||
}, | ||
test: { | ||
// This is needed until Next will update to the React 19 beta: https://github.com/vercel/next.js/pull/65058 | ||
// In the React 19 beta ErrorBoundary errors (such as redirect) are only logged, and not thrown. | ||
// We will also suspress console.error logs for re the console.error logs for redirect in the next framework. | ||
// Using the onCaughtError react root option: | ||
// react: { | ||
// rootOptions: { | ||
// onCaughtError(error: unknown) { | ||
// if (isNextRouterError(error)) return; | ||
// console.error(error); | ||
// }, | ||
// }, | ||
// See: code/frameworks/nextjs/src/preview.tsx | ||
dangerouslyIgnoreUnhandledErrors: true, | ||
}, | ||
}, | ||
} as Meta<typeof Component>; | ||
|
||
type Story = StoryObj<typeof Component>; | ||
|
||
export const ProtectedWhileLoggedOut: Story = { | ||
play: async ({ canvasElement }) => { | ||
const canvas = within(canvasElement); | ||
await userEvent.click(canvas.getByText('Access protected route')); | ||
|
||
await expect(cookies().get).toHaveBeenCalledWith('user'); | ||
await expect(redirect).toHaveBeenCalledWith('/'); | ||
|
||
await waitFor(() => expect(getRouter().push).toHaveBeenCalled()); | ||
}, | ||
}; | ||
|
||
export const ProtectedWhileLoggedIn: Story = { | ||
beforeEach() { | ||
cookies().set('user', 'storybookjs'); | ||
}, | ||
play: async ({ canvasElement }) => { | ||
const canvas = within(canvasElement); | ||
await userEvent.click(canvas.getByText('Access protected route')); | ||
|
||
await expect(cookies().get).toHaveBeenLastCalledWith('user'); | ||
await expect(revalidatePath).toHaveBeenLastCalledWith('/'); | ||
await expect(redirect).toHaveBeenLastCalledWith('/protected'); | ||
|
||
await waitFor(() => expect(getRouter().push).toHaveBeenCalled()); | ||
}, | ||
}; | ||
|
||
export const Logout: Story = { | ||
beforeEach() { | ||
cookies().set('user', 'storybookjs'); | ||
}, | ||
play: async ({ canvasElement }) => { | ||
const canvas = within(canvasElement); | ||
|
||
await userEvent.click(canvas.getByText('Logout')); | ||
await expect(cookies().delete).toHaveBeenCalled(); | ||
await expect(revalidatePath).toHaveBeenCalledWith('/'); | ||
await expect(redirect).toHaveBeenCalledWith('/'); | ||
|
||
await waitFor(() => expect(getRouter().push).toHaveBeenCalled()); | ||
}, | ||
}; | ||
|
||
export const Login: Story = { | ||
play: async ({ canvasElement }) => { | ||
const canvas = within(canvasElement); | ||
await userEvent.click(canvas.getByText('Login')); | ||
|
||
await expect(cookies().set).toHaveBeenCalledWith('user', 'storybookjs'); | ||
await expect(revalidatePath).toHaveBeenCalledWith('/'); | ||
await expect(redirect).toHaveBeenCalledWith('/'); | ||
|
||
await waitFor(() => expect(getRouter().push).toHaveBeenCalled()); | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
'use server'; | ||
|
||
import { revalidatePath } from 'next/cache'; | ||
import { cookies } from 'next/headers'; | ||
import { redirect } from 'next/navigation'; | ||
|
||
export async function accessRoute() { | ||
const user = (await cookies()).get('user'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Potential race condition - cookies() is called twice, once for await and once for get(). Use const cookieStore = await cookies() first |
||
|
||
if (!user) { | ||
redirect('/'); | ||
} | ||
|
||
revalidatePath('/'); | ||
redirect(`/protected`); | ||
Comment on lines
+14
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: Calling revalidatePath() right before redirect() is unnecessary since the redirect will trigger a full page load |
||
} | ||
|
||
export async function logout() { | ||
(await cookies()).delete('user'); | ||
revalidatePath('/'); | ||
redirect('/'); | ||
} | ||
|
||
export async function login() { | ||
(await cookies()).set('user', 'storybookjs'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: No expiration time set for cookie, could lead to indefinite sessions |
||
revalidatePath('/'); | ||
redirect('/'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
syntax: typo in 'suspress' - should be 'suppress'