Skip to content

Commit

Permalink
Add and use spyOnHandler to expect MSW handler calls (#1127)
Browse files Browse the repository at this point in the history
  • Loading branch information
oliver3 authored Mar 5, 2025
1 parent 624daf3 commit b819a8c
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 138 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { userEvent } from "@testing-library/user-event";
import { beforeEach, describe, expect, test, vi } from "vitest";
import { beforeEach, describe, expect, test } from "vitest";

import { ElectionProvider, PollingStationResults } from "@kiesraad/api";
import {
electionMockData,
ElectionRequestHandler,
PollingStationDataEntryFinaliseHandler,
PollingStationDataEntryGetHandler,
PollingStationDataEntrySaveHandler,
} from "@kiesraad/api-mocks";
import { overrideOnce, renderReturningRouter, screen, server, within } from "@kiesraad/test";
import { renderReturningRouter, screen, server, spyOnHandler, within } from "@kiesraad/test";

import { defaultFormState, emptyDataEntryRequest, errorWarningMocks } from "../../testHelperFunctions";
import { FormState, PollingStationFormController } from "../PollingStationFormController";
Expand All @@ -34,27 +35,26 @@ function renderForm(defaultFormState: Partial<FormState> = {}, defaultValues?: P

describe("Test CheckAndSaveForm", () => {
beforeEach(() => {
server.use(ElectionRequestHandler, PollingStationDataEntryGetHandler, PollingStationDataEntrySaveHandler);
server.use(
ElectionRequestHandler,
PollingStationDataEntryGetHandler,
PollingStationDataEntrySaveHandler,
PollingStationDataEntryFinaliseHandler,
);
});

test("Data entry can be finalised", async () => {
const router = renderForm();
const user = userEvent.setup();

// set up a listener to check if the finalisation request is made
let request_method, request_url;
overrideOnce("post", "/api/polling_stations/1/data_entries/1/finalise", 200, null);
server.events.on("request:start", ({ request }) => {
request_method = request.method;
request_url = request.url;
});
const finalise = spyOnHandler(PollingStationDataEntryFinaliseHandler);

// click the save button
await user.click(await screen.findByRole("button", { name: "Opslaan" }));

// check that the finalisation request was made
expect(request_method).toBe("POST");
expect(request_url).toBe("http://localhost:3000/api/polling_stations/1/data_entries/1/finalise");
expect(finalise).toHaveBeenCalledOnce();

// check that the user is navigated back to the data entry page
expect(router.state.location.pathname).toEqual("/elections/1/data-entry");
Expand All @@ -63,17 +63,15 @@ describe("Test CheckAndSaveForm", () => {

test("Shift+Enter submits form", async () => {
renderForm();
const finalise = spyOnHandler(PollingStationDataEntryFinaliseHandler);

expect(await screen.findByRole("group", { name: "Controleren en opslaan" }));

overrideOnce("post", "/api/polling_stations/1/data_entries/1/finalise", 200, null);

const user = userEvent.setup();
const spy = vi.spyOn(global, "fetch");

await user.keyboard("{shift>}{enter}{/shift}");

expect(spy).toHaveBeenCalled();
expect(finalise).toHaveBeenCalled();
});

test("Data entry does not show finalise button with errors", async () => {
Expand Down
25 changes: 6 additions & 19 deletions frontend/app/component/form/user/login/LoginForm.test.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,17 @@
import userEvent from "@testing-library/user-event";
import { describe, expect, test } from "vitest";

import { overrideOnce, render, screen, waitFor } from "@kiesraad/test";
import { LoginHandler } from "@kiesraad/api-mocks";
import { overrideOnce, render, screen, server, spyOnHandler, waitFor } from "@kiesraad/test";

import { LoginForm } from "./LoginForm";

describe("LoginForm", () => {
test("Successful login", async () => {
render(<LoginForm />);
let requestBody: object | null = null;
server.use(LoginHandler);
const login = spyOnHandler(LoginHandler);

overrideOnce(
"post",
"/api/user/login",
200,
{
user_id: 1,
username: "admin",
},
undefined,
async (request) => {
requestBody = (await request.json()) as object;
},
);
render(<LoginForm />);

const user = userEvent.setup();

Expand All @@ -34,9 +23,7 @@ describe("LoginForm", () => {
const submitButton = screen.getByRole("button", { name: "Inloggen" });
await user.click(submitButton);

await waitFor(() => {
expect(requestBody).toStrictEqual({ username: "user", password: "password" });
});
expect(login).toHaveBeenCalledWith({ username: "user", password: "password" });
});

test("Unsuccessful login", async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
import { userEvent } from "@testing-library/user-event";
import { http, HttpResponse } from "msw";
import { beforeEach, describe, expect, test } from "vitest";

import {
ElectionProvider,
ErrorResponse,
POLLING_STATION_DATA_ENTRY_SAVE_REQUEST_BODY,
SaveDataEntryResponse,
} from "@kiesraad/api";
import { ElectionProvider } from "@kiesraad/api";
import {
electionMockData,
ElectionRequestHandler,
PollingStationDataEntryDeleteHandler,
PollingStationDataEntryGetHandler,
PollingStationDataEntrySaveHandler,
} from "@kiesraad/api-mocks";
import { renderReturningRouter, screen, server } from "@kiesraad/test";
import { renderReturningRouter, screen, server, spyOnHandler } from "@kiesraad/test";

import { PollingStationFormController } from "../../../component/form/data_entry/PollingStationFormController";
import { VotersAndVotesForm } from "../../../component/form/data_entry/voters_and_votes/VotersAndVotesForm";
Expand Down Expand Up @@ -63,37 +57,31 @@ describe("Test AbortDataEntryControl", () => {
// fill in the form
await user.type(await screen.findByTestId("poll_card_count"), "42");

// set up a custom request handler that saves the request body
// this cannot be done with a listener because it would consume the request body
let request_body: POLLING_STATION_DATA_ENTRY_SAVE_REQUEST_BODY | undefined;
server.use(
http.post<never, POLLING_STATION_DATA_ENTRY_SAVE_REQUEST_BODY, SaveDataEntryResponse | ErrorResponse>(
"/api/polling_stations/1/data_entries/1",
async ({ request }) => {
request_body = await request.json();
return HttpResponse.json({ validation_results: { errors: [], warnings: [] } }, { status: 200 });
},
{ once: true },
),
);
const dataEntrySave = spyOnHandler(PollingStationDataEntrySaveHandler);

// click the save button in the modal
await user.click(screen.getByRole("button", { name: "Invoer bewaren" }));

// check that the save request was made with the correct data
expect(request_body?.data).toEqual({
...emptyDataEntryRequest.data,
voters_counts: {
...emptyDataEntryRequest.data.voters_counts,
poll_card_count: 42,
},
});
expect(dataEntrySave).toHaveBeenCalledWith(
expect.objectContaining({
data: {
...emptyDataEntryRequest.data,
voters_counts: {
...emptyDataEntryRequest.data.voters_counts,
poll_card_count: 42,
},
},
}),
);

// check that the user is navigated back to the data entry page
expect(router.state.location.pathname).toBe("/elections/1/data-entry");
});

test("deletes the data entry and navigates on delete", async () => {
server.use(PollingStationDataEntryDeleteHandler);

// render the abort button, then click it to open the modal
const router = renderAbortDataEntryControl();
const user = userEvent.setup();
Expand All @@ -103,19 +91,13 @@ describe("Test AbortDataEntryControl", () => {
await user.click(abortButton);

// set up a listener to check if the delete request is made
let request_method, request_url;
server.use(PollingStationDataEntryDeleteHandler);
server.events.on("request:start", ({ request }) => {
request_method = request.method;
request_url = request.url;
});
const dataEntryDelete = spyOnHandler(PollingStationDataEntryDeleteHandler);

// click the delete button in the modal
await user.click(screen.getByRole("button", { name: "Niet bewaren" }));

// check that the delete request was made and the user is navigated back to the data entry page
expect(request_method).toBe("DELETE");
expect(request_url).toBe("http://localhost:3000/api/polling_stations/1/data_entries/1");
expect(dataEntryDelete).toHaveBeenCalled();

// check that the user is navigated back to the data entry page
expect(router.state.location.pathname).toBe("/elections/1/data-entry");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ import { userEvent } from "@testing-library/user-event";
import { beforeEach, describe, expect, test, vi } from "vitest";

import { ElectionProvider, PollingStation } from "@kiesraad/api";
import { ElectionRequestHandler, PollingStationUpdateHandler } from "@kiesraad/api-mocks";
import { overrideOnce, render, renderReturningRouter, server } from "@kiesraad/test";
import {
ElectionRequestHandler,
PollingStationDeleteHandler,
PollingStationGetHandler,
PollingStationUpdateHandler,
} from "@kiesraad/api-mocks";
import { overrideOnce, render, renderReturningRouter, server, spyOnHandler } from "@kiesraad/test";

import { PollingStationUpdatePage } from "./PollingStationUpdatePage";

Expand All @@ -28,17 +33,10 @@ describe("PollingStationUpdatePage", () => {
};

beforeEach(() => {
server.use(ElectionRequestHandler, PollingStationUpdateHandler);
server.use(ElectionRequestHandler, PollingStationGetHandler, PollingStationUpdateHandler);
});

test("Shows form", async () => {
overrideOnce(
"get",
`/api/elections/${testPollingStation.election_id}/polling_stations/${testPollingStation.id}`,
200,
testPollingStation,
);

render(
<ElectionProvider electionId={1}>
<PollingStationUpdatePage />
Expand All @@ -48,18 +46,11 @@ describe("PollingStationUpdatePage", () => {
const form = await screen.findByTestId("polling-station-form");
expect(form).toBeVisible();

expect(screen.getByRole("textbox", { name: "Nummer" })).toHaveValue("1");
expect(screen.getByRole("textbox", { name: "Naam" })).toHaveValue("test");
expect(screen.getByRole("textbox", { name: "Nummer" })).toHaveValue("33");
expect(screen.getByRole("textbox", { name: "Naam" })).toHaveValue("Op Rolletjes");
});

test("Navigates back on save", async () => {
overrideOnce(
"get",
`/api/elections/${testPollingStation.election_id}/polling_stations/${testPollingStation.id}`,
200,
testPollingStation,
);

const router = renderReturningRouter(
<ElectionProvider electionId={1}>
<PollingStationUpdatePage />
Expand All @@ -77,15 +68,9 @@ describe("PollingStationUpdatePage", () => {

describe("Delete polling station", () => {
test("Returns to list page with a message", async () => {
server.use(PollingStationDeleteHandler);
const user = userEvent.setup();

overrideOnce(
"get",
`/api/elections/${testPollingStation.election_id}/polling_stations/${testPollingStation.id}`,
200,
testPollingStation,
);

const router = renderReturningRouter(
<ElectionProvider electionId={1}>
<PollingStationUpdatePage />
Expand All @@ -98,40 +83,21 @@ describe("PollingStationUpdatePage", () => {
const modal = await screen.findByTestId("modal-dialog");
expect(modal).toHaveTextContent("Stembureau verwijderen");

let request_method: string;
let request_url: string;

overrideOnce(
"delete",
`/api/elections/${testPollingStation.election_id}/polling_stations/${testPollingStation.id}`,
200,
"",
);

server.events.on("request:start", ({ request }) => {
request_method = request.method;
request_url = request.url;
});
const deletePollingStation = spyOnHandler(PollingStationDeleteHandler);

const confirmButton = await within(modal).findByRole("button", { name: "Verwijderen" });
await user.click(confirmButton);

await waitFor(() => {
expect(request_method).toEqual("DELETE");
expect(request_url).toContain(
`/api/elections/${testPollingStation.election_id}/polling_stations/${testPollingStation.id}`,
);
});
expect(deletePollingStation).toHaveBeenCalled();

expect(router.state.location.pathname).toEqual("/elections/1/polling-stations");
expect(router.state.location.search).toEqual("?deleted=1%20(test)");
expect(router.state.location.search).toEqual("?deleted=33%20(Op%20Rolletjes)");
});

test("Shows an error message when delete was not possible", async () => {
const user = userEvent.setup();

const url = `/api/elections/${testPollingStation.election_id}/polling_stations/${testPollingStation.id}`;
overrideOnce("get", url, 200, testPollingStation);
overrideOnce("delete", url, 422, {
error: "Invalid data",
fatal: false,
Expand Down
Loading

0 comments on commit b819a8c

Please sign in to comment.