mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
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:
@@ -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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user