test: migrate CreateUserPage tests to Storybook play stories (#26794)

Migrates the CreateUserPage tests from vitest to Storybook play-function
stories.

The old `CreateUserPage.test.tsx` rendered the full page through
`renderWithAuth` and MSW, which is slow and contributes to `test-js`
timeout flakes. The new stories seed the react-query cache directly and
assert the same behavior in `play` functions, so they run in the
Storybook test lane instead of the vitest `unit` project.

Coverage is preserved: a success story asserts the success toast after
creating a user, and an error story asserts that an API failure surfaces
in the form's error alert.

Relates to CODAGT-686
Relates to https://github.com/coder/internal/issues/1598
This commit is contained in:
Ethan
2026-06-29 18:24:23 +10:00
committed by GitHub
parent c782cbce77
commit 4820cbf7b1
2 changed files with 73 additions and 67 deletions
@@ -0,0 +1,73 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { spyOn, userEvent, within } from "storybook/test";
import { API } from "#/api/api";
import { rolesQueryKey } from "#/api/queries/roles";
import { authMethodsQueryKey } from "#/api/queries/users";
import {
MockAuthMethodsPasswordOnly,
MockUserMember,
mockApiError,
} from "#/testHelpers/entities";
import { withDashboardProvider, withToaster } from "#/testHelpers/storybook";
import CreateUserPage from "./CreateUserPage";
const meta = {
title: "pages/CreateUserPage/CreateUserPage",
component: CreateUserPage,
decorators: [withToaster, withDashboardProvider],
parameters: {
queries: [
{ key: authMethodsQueryKey, data: MockAuthMethodsPasswordOnly },
{ key: rolesQueryKey, data: [] },
],
},
} satisfies Meta<typeof CreateUserPage>;
export default meta;
type Story = StoryObj<typeof meta>;
export const ShowsSuccessNotificationOnSubmit: Story = {
beforeEach: () => {
spyOn(API, "createUser").mockResolvedValue(MockUserMember);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const user = userEvent.setup();
await fillForm(canvas, user);
await within(document.body).findByText(
'User "someuser" created successfully.',
);
},
};
export const ShowsErrorWhenUserCreationFails: Story = {
beforeEach: () => {
spyOn(API, "createUser").mockRejectedValue(
mockApiError({ message: "Username already in use." }),
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const user = userEvent.setup();
await fillForm(canvas, user);
await canvas.findAllByText("Username already in use.");
},
};
async function fillForm(
canvas: ReturnType<typeof within>,
user: ReturnType<typeof userEvent.setup>,
) {
await user.type(await canvas.findByLabelText("Username"), "someuser");
await user.type(canvas.getByLabelText(/email/i), "someone@coder.com");
const body = within(document.body);
await user.click(canvas.getByTestId("login-type-input"));
await user.click(await body.findByRole("option", { name: /password/i }));
await user.type(
await canvas.findByTestId("password-input"),
"SomeSecurePassword!",
);
await user.click(canvas.getByRole("button", { name: /save/i }));
}
@@ -1,67 +0,0 @@
import { screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { HttpResponse, http } from "msw";
import { describe, expect, it } from "vitest";
import {
renderWithAuth,
waitForLoaderToBeRemoved,
} from "#/testHelpers/renderHelpers";
import { server } from "#/testHelpers/server";
import CreateUserPage from "./CreateUserPage";
const fillForm = async ({
username = "someuser",
email = "someone@coder.com",
password = "SomeSecurePassword!",
}: {
username?: string;
email?: string;
password?: string;
} = {}) => {
await userEvent.type(screen.getByLabelText("Username"), username);
await userEvent.type(screen.getByLabelText(/email/i), email);
await userEvent.click(screen.getByTestId("login-type-input"));
await userEvent.click(screen.getByRole("option", { name: /password/i }));
await userEvent.type(screen.getByTestId("password-input"), password);
await userEvent.click(screen.getByRole("button", { name: /save/i }));
};
describe("CreateUserPage", () => {
it("shows a success notification and redirects to the users page on submit", async () => {
renderWithAuth(<CreateUserPage />, {
extraRoutes: [
{ path: "/deployment/users", element: <div>Users Page</div> },
],
});
await waitForLoaderToBeRemoved();
await fillForm();
await expect(
screen.findByText('User "someuser" created successfully.'),
).resolves.toBeInTheDocument();
});
it("shows an error alert when user creation fails", async () => {
server.use(
http.post("/api/v2/users", () => {
return HttpResponse.json(
{ message: "Username already in use." },
{ status: 400 },
);
}),
);
renderWithAuth(<CreateUserPage />, {
extraRoutes: [
{ path: "/deployment/users", element: <div>Users Page</div> },
],
});
await waitForLoaderToBeRemoved();
await fillForm();
await expect(
screen.findByText("Username already in use."),
).resolves.toBeInTheDocument();
});
});