+
- {data.validation_results.warnings.map((warning, n) => (
+ {warnings.map((warning, n) => (
- {warning.code}
))}
)}
-
- {data && !hasValidationError && (
-
-
-
Success
-
-
- )}
Veld |
@@ -188,71 +136,78 @@ export function DifferencesForm() {
diff --git a/frontend/app/component/form/voters_and_votes/VotersAndVotesForm.test.tsx b/frontend/app/component/form/voters_and_votes/VotersAndVotesForm.test.tsx
index acf90fc34..3aed47dbe 100644
--- a/frontend/app/component/form/voters_and_votes/VotersAndVotesForm.test.tsx
+++ b/frontend/app/component/form/voters_and_votes/VotersAndVotesForm.test.tsx
@@ -2,121 +2,160 @@
* @vitest-environment jsdom
*/
-import { overrideOnce, render, screen, fireEvent } from "app/test/unit";
+import { overrideOnce, render, screen, getUrlMethodAndBody } from "app/test/unit";
import { userEvent } from "@testing-library/user-event";
import { describe, expect, test, vi, afterEach } from "vitest";
+
+import {
+ POLLING_STATION_DATA_ENTRY_REQUEST_BODY,
+ PollingStationFormController,
+} from "@kiesraad/api";
+import { electionMock } from "@kiesraad/api-mocks";
import { VotersAndVotesForm } from "./VotersAndVotesForm";
+const Component = (
+
+
+
+);
+
+const rootRequest: POLLING_STATION_DATA_ENTRY_REQUEST_BODY = {
+ data: {
+ political_group_votes: electionMock.political_groups.map((group) => ({
+ number: group.number,
+ total: 0,
+ candidate_votes: group.candidates.map((candidate) => ({
+ number: candidate.number,
+ votes: 0,
+ })),
+ })),
+ differences_counts: {
+ more_ballots_count: 0,
+ fewer_ballots_count: 0,
+ unreturned_ballots_count: 0,
+ too_few_ballots_handed_out_count: 0,
+ too_many_ballots_handed_out_count: 0,
+ other_explanation_count: 0,
+ no_explanation_count: 0,
+ },
+ voters_counts: {
+ poll_card_count: 0,
+ proxy_certificate_count: 0,
+ voter_card_count: 0,
+ total_admitted_voters_count: 0,
+ },
+ votes_counts: {
+ votes_candidates_counts: 0,
+ blank_votes_count: 0,
+ invalid_votes_count: 0,
+ total_votes_cast_count: 0,
+ },
+ },
+};
+
describe("Test VotersAndVotesForm", () => {
afterEach(() => {
vi.restoreAllMocks(); // ToDo: tests pass without this, so not needed?
});
- test("hitting enter key does not result in api call", async () => {
- const spy = vi.spyOn(global, "fetch");
-
- const user = userEvent.setup();
+ describe("VotersAndVotesForm user interactions", () => {
+ test("hitting enter key does not result in api call", async () => {
+ const spy = vi.spyOn(global, "fetch");
- render(
);
+ const user = userEvent.setup();
- const pollCards = screen.getByTestId("poll_card_count");
- await user.clear(pollCards);
- await user.type(pollCards, "12345");
- expect(pollCards).toHaveValue("12.345");
+ render(Component);
- await user.keyboard("{enter}");
+ const pollCards = screen.getByTestId("poll_card_count");
+ await user.type(pollCards, "12345");
+ expect(pollCards).toHaveValue("12.345");
- expect(spy).not.toHaveBeenCalled();
- });
+ await user.keyboard("{enter}");
- test("Form field entry and keybindings", async () => {
- overrideOnce("post", "/v1/api/polling_stations/1/data_entries/1", 200, {
- message: "Data saved",
- saved: true,
- validation_results: { errors: [], warnings: [] },
+ expect(spy).not.toHaveBeenCalled();
});
- const user = userEvent.setup();
+ test("Form field entry and keybindings", async () => {
+ overrideOnce("post", "/v1/api/polling_stations/1/data_entries/1", 200, {
+ message: "Data saved",
+ saved: true,
+ validation_results: { errors: [], warnings: [] },
+ });
- render(
);
+ const user = userEvent.setup();
- const pollCards = screen.getByTestId("poll_card_count");
- expect(pollCards).toHaveFocus();
- await user.clear(pollCards);
- await user.type(pollCards, "12345");
- expect(pollCards).toHaveValue("12.345");
+ render(Component);
- await user.keyboard("{enter}");
+ const pollCards = screen.getByTestId("poll_card_count");
+ expect(pollCards).toHaveFocus();
+ await user.type(pollCards, "12345");
+ expect(pollCards).toHaveValue("12.345");
- const proxyCertificates = screen.getByTestId("proxy_certificate_count");
- expect(proxyCertificates).toHaveFocus();
- await user.clear(proxyCertificates);
- await user.paste("6789");
- expect(proxyCertificates).toHaveValue("6.789");
+ await user.keyboard("{enter}");
- await user.keyboard("{enter}");
+ const proxyCertificates = screen.getByTestId("proxy_certificate_count");
+ expect(proxyCertificates).toHaveFocus();
+ await user.paste("6789");
+ expect(proxyCertificates).toHaveValue("6.789");
- const voterCards = screen.getByTestId("voter_card_count");
- expect(voterCards).toHaveFocus();
- await user.clear(voterCards);
- await user.type(voterCards, "123");
- expect(voterCards).toHaveValue("123");
+ await user.keyboard("{enter}");
- await user.keyboard("{enter}");
+ const voterCards = screen.getByTestId("voter_card_count");
+ expect(voterCards).toHaveFocus();
+ await user.type(voterCards, "123");
+ expect(voterCards).toHaveValue("123");
- const totalAdmittedVoters = screen.getByTestId("total_admitted_voters_count");
- expect(totalAdmittedVoters).toHaveFocus();
- await user.clear(totalAdmittedVoters);
- await user.paste("4242");
- expect(totalAdmittedVoters).toHaveValue("4.242");
+ await user.keyboard("{enter}");
- await user.keyboard("{enter}");
+ const totalAdmittedVoters = screen.getByTestId("total_admitted_voters_count");
+ expect(totalAdmittedVoters).toHaveFocus();
+ await user.paste("4242");
+ expect(totalAdmittedVoters).toHaveValue("4.242");
- const votesOnCandidates = screen.getByTestId("votes_candidates_counts");
- expect(votesOnCandidates).toHaveFocus();
- await user.clear(votesOnCandidates);
- await user.type(votesOnCandidates, "12");
- expect(votesOnCandidates).toHaveValue("12");
+ await user.keyboard("{enter}");
- await user.keyboard("{enter}");
+ const votesOnCandidates = screen.getByTestId("votes_candidates_counts");
+ expect(votesOnCandidates).toHaveFocus();
+ await user.type(votesOnCandidates, "12");
+ expect(votesOnCandidates).toHaveValue("12");
- const blankVotes = screen.getByTestId("blank_votes_count");
- expect(blankVotes).toHaveFocus();
- await user.clear(blankVotes);
- // Test if maxLength on field works
- await user.type(blankVotes, "1000000000");
- expect(blankVotes).toHaveValue("100.000.000");
+ await user.keyboard("{enter}");
- await user.keyboard("{enter}");
+ const blankVotes = screen.getByTestId("blank_votes_count");
+ expect(blankVotes).toHaveFocus();
+ // Test if maxLength on field works
+ await user.type(blankVotes, "1000000000");
+ expect(blankVotes).toHaveValue("100.000.000");
- const invalidVotes = screen.getByTestId("invalid_votes_count");
- expect(invalidVotes).toHaveFocus();
- await user.clear(invalidVotes);
- await user.type(invalidVotes, "3");
- expect(invalidVotes).toHaveValue("3");
+ await user.keyboard("{enter}");
- await user.keyboard("{enter}");
+ const invalidVotes = screen.getByTestId("invalid_votes_count");
+ expect(invalidVotes).toHaveFocus();
+ await user.type(invalidVotes, "3");
+ expect(invalidVotes).toHaveValue("3");
- const totalVotesCast = screen.getByTestId("total_votes_cast_count");
- expect(totalVotesCast).toHaveFocus();
- await user.clear(totalVotesCast);
- await user.type(totalVotesCast, "555");
- expect(totalVotesCast).toHaveValue("555");
+ await user.keyboard("{enter}");
- const submitButton = screen.getByRole("button", { name: "Volgende" });
- await user.click(submitButton);
+ const totalVotesCast = screen.getByTestId("total_votes_cast_count");
+ expect(totalVotesCast).toHaveFocus();
+ await user.type(totalVotesCast, "555");
+ expect(totalVotesCast).toHaveValue("555");
- const result = await screen.findByTestId("result");
+ const submitButton = screen.getByRole("button", { name: "Volgende" });
+ await user.click(submitButton);
- expect(result).toHaveTextContent(/^Success$/);
+ const result = await screen.findByTestId("result");
+ expect(result).toHaveTextContent(/^Success$/);
+ });
});
- describe("VotersAndVotesForm Api call", () => {
+ describe("VotersAndVotesForm API request and response", () => {
test("VotersAndVotesForm request body is equal to the form data", async () => {
const spy = vi.spyOn(global, "fetch");
const expectedRequest = {
data: {
+ ...rootRequest.data,
voters_counts: {
poll_card_count: 1,
proxy_certificate_count: 2,
@@ -129,143 +168,349 @@ describe("Test VotersAndVotesForm", () => {
invalid_votes_count: 6,
total_votes_cast_count: 15,
},
- differences_counts: {
- more_ballots_count: 0,
- fewer_ballots_count: 0,
- unreturned_ballots_count: 0,
- too_few_ballots_handed_out_count: 0,
- too_many_ballots_handed_out_count: 0,
- other_explanation_count: 0,
- no_explanation_count: 0,
- },
- political_group_votes: [
- {
- candidate_votes: [{ number: 1, votes: 0 }],
- number: 1,
- total: 0,
- },
- ],
},
};
const user = userEvent.setup();
- const { getByTestId } = render(
);
+ render(Component);
+
+ await user.type(
+ screen.getByTestId("poll_card_count"),
+ expectedRequest.data.voters_counts.poll_card_count.toString(),
+ );
+ await user.type(
+ screen.getByTestId("proxy_certificate_count"),
+ expectedRequest.data.voters_counts.proxy_certificate_count.toString(),
+ );
+ await user.type(
+ screen.getByTestId("voter_card_count"),
+ expectedRequest.data.voters_counts.voter_card_count.toString(),
+ );
+ await user.type(
+ screen.getByTestId("total_admitted_voters_count"),
+ expectedRequest.data.voters_counts.total_admitted_voters_count.toString(),
+ );
+
+ await user.type(
+ screen.getByTestId("votes_candidates_counts"),
+ expectedRequest.data.votes_counts.votes_candidates_counts.toString(),
+ );
+ await user.type(
+ screen.getByTestId("blank_votes_count"),
+ expectedRequest.data.votes_counts.blank_votes_count.toString(),
+ );
+ await user.type(
+ screen.getByTestId("invalid_votes_count"),
+ expectedRequest.data.votes_counts.invalid_votes_count.toString(),
+ );
+ await user.type(
+ screen.getByTestId("total_votes_cast_count"),
+ expectedRequest.data.votes_counts.total_votes_cast_count.toString(),
+ );
+
+ const submitButton = screen.getByRole("button", { name: "Volgende" });
+ await user.click(submitButton);
+
+ expect(spy).toHaveBeenCalled();
+ const { url, method, body } = getUrlMethodAndBody(spy.mock.calls);
+
+ expect(url).toEqual("http://testhost/v1/api/polling_stations/1/data_entries/1");
+ expect(method).toEqual("POST");
+ expect(body).toEqual(expectedRequest);
- const pollCards = getByTestId("poll_card_count");
- fireEvent.change(pollCards, {
- target: { value: expectedRequest.data.voters_counts.poll_card_count.toString() },
+ const result = await screen.findByTestId("result");
+ expect(result).toHaveTextContent(/^Success$/);
+ });
+
+ test("422 response results in display of error message", async () => {
+ overrideOnce("post", "/v1/api/polling_stations/1/data_entries/1", 422, {
+ message: "422 error from mock",
});
- const proxyCertificates = getByTestId("proxy_certificate_count");
- fireEvent.change(proxyCertificates, {
- target: { value: expectedRequest.data.voters_counts.proxy_certificate_count.toString() },
+ const user = userEvent.setup();
+
+ render(Component);
+
+ const submitButton = screen.getByRole("button", { name: "Volgende" });
+ await user.click(submitButton);
+ const feedbackServerError = await screen.findByTestId("feedback-server-error");
+ expect(feedbackServerError).toHaveTextContent(/^Error422 error from mock$/);
+
+ expect(screen.queryByTestId("result")).not.toBeNull();
+ expect(screen.queryByTestId("result")).toHaveTextContent(/^422 error from mock$/);
+ });
+
+ test("500 response results in display of error message", async () => {
+ overrideOnce("post", "/v1/api/polling_stations/1/data_entries/1", 500, {
+ message: "500 error from mock",
+ errorCode: "500_ERROR",
});
- const voterCards = getByTestId("voter_card_count");
- fireEvent.change(voterCards, {
- target: { value: expectedRequest.data.voters_counts.voter_card_count.toString() },
+ const user = userEvent.setup();
+
+ render(Component);
+
+ const submitButton = screen.getByRole("button", { name: "Volgende" });
+ await user.click(submitButton);
+ const feedbackServerError = await screen.findByTestId("feedback-server-error");
+ expect(feedbackServerError).toHaveTextContent(/^Error500 error from mock$/);
+
+ expect(screen.queryByTestId("result")).not.toBeNull();
+ expect(screen.queryByTestId("result")).toHaveTextContent(/^500 error from mock$/);
+ });
+ });
+
+ describe("VotersAndVotesForm errors", () => {
+ test("F.01 Invalid value", async () => {
+ overrideOnce("post", "/v1/api/polling_stations/1/data_entries/1", 422, {
+ error:
+ "Failed to deserialize the JSON body into the target type: data.voters_counts.poll_card_count: invalid value: integer `-3`, expected u32 at line 1 column 525",
});
- const totalAdmittedVoters = getByTestId("total_admitted_voters_count");
- fireEvent.change(totalAdmittedVoters, {
- target: {
- value: expectedRequest.data.voters_counts.total_admitted_voters_count.toString(),
+ const user = userEvent.setup();
+
+ render(Component);
+
+ // Since the component does not allow to input invalid values such as -3,
+ // not inputting any values and just clicking the submit button.
+ const submitButton = screen.getByRole("button", { name: "Volgende" });
+ await user.click(submitButton);
+
+ const feedbackServerError = await screen.findByTestId("feedback-server-error");
+ expect(feedbackServerError).toHaveTextContent(/^Error$/);
+ expect(screen.queryByTestId("feedback-warning")).toBeNull();
+ expect(screen.queryByTestId("feedback-error")).toBeNull();
+ });
+
+ test("F.11 IncorrectTotal Voters", async () => {
+ overrideOnce("post", "/v1/api/polling_stations/1/data_entries/1", 200, {
+ saved: true,
+ message: "Data entry saved successfully",
+ validation_results: {
+ errors: [
+ {
+ fields: [
+ "data.voters_counts.total_admitted_voters_count",
+ "data.voters_counts.poll_card_count",
+ "data.voters_counts.proxy_certificate_count",
+ "data.voters_counts.voter_card_count",
+ ],
+ code: "IncorrectTotal",
+ },
+ ],
+ warnings: [],
},
});
- const votesOnCandidates = getByTestId("votes_candidates_counts");
- fireEvent.change(votesOnCandidates, {
- target: { value: expectedRequest.data.votes_counts.votes_candidates_counts.toString() },
- });
+ const user = userEvent.setup();
- const blankVotes = getByTestId("blank_votes_count");
- fireEvent.change(blankVotes, {
- target: { value: expectedRequest.data.votes_counts.blank_votes_count.toString() },
- });
+ render(Component);
- const invalidVotes = getByTestId("invalid_votes_count");
- fireEvent.change(invalidVotes, {
- target: { value: expectedRequest.data.votes_counts.invalid_votes_count.toString() },
- });
+ await user.type(screen.getByTestId("poll_card_count"), "1");
+ await user.type(screen.getByTestId("proxy_certificate_count"), "1");
+ await user.type(screen.getByTestId("voter_card_count"), "1");
+ await user.type(screen.getByTestId("total_admitted_voters_count"), "4");
+
+ const submitButton = screen.getByRole("button", { name: "Volgende" });
+ await user.click(submitButton);
- const totalVotesCast = getByTestId("total_votes_cast_count");
- fireEvent.change(totalVotesCast, {
- target: { value: expectedRequest.data.votes_counts.total_votes_cast_count.toString() },
+ const feedbackError = await screen.findByTestId("feedback-error");
+ expect(feedbackError).toHaveTextContent(/^IncorrectTotal$/);
+ expect(screen.queryByTestId("feedback-warning")).toBeNull();
+ expect(screen.queryByTestId("server-feedback-error")).toBeNull();
+ });
+
+ test("F.12 IncorrectTotal Votes", async () => {
+ overrideOnce("post", "/v1/api/polling_stations/1/data_entries/1", 200, {
+ saved: true,
+ message: "Data entry saved successfully",
+ validation_results: {
+ errors: [
+ {
+ fields: [
+ "data.votes_counts.total_votes_cast_count",
+ "data.votes_counts.votes_candidates_counts",
+ "data.votes_counts.blank_votes_count",
+ "data.votes_counts.invalid_votes_count",
+ ],
+ code: "IncorrectTotal",
+ },
+ {
+ fields: ["data.votes_counts.votes_candidates_counts", "data.political_group_votes"],
+ code: "IncorrectTotal",
+ },
+ ],
+ warnings: [],
+ },
});
+ const user = userEvent.setup();
+
+ render(Component);
+
+ await user.type(screen.getByTestId("votes_candidates_counts"), "1");
+ await user.type(screen.getByTestId("blank_votes_count"), "1");
+ await user.type(screen.getByTestId("invalid_votes_count"), "1");
+ await user.type(screen.getByTestId("total_votes_cast_count"), "4");
+
const submitButton = screen.getByRole("button", { name: "Volgende" });
await user.click(submitButton);
- expect(spy).toHaveBeenCalledWith("http://testhost/v1/api/polling_stations/1/data_entries/1", {
- method: "POST",
- body: JSON.stringify(expectedRequest),
- headers: {
- "Content-Type": "application/json",
+ const feedbackError = await screen.findByTestId("feedback-error");
+ expect(feedbackError).toHaveTextContent(/^IncorrectTotal$/);
+ expect(screen.queryByTestId("feedback-warning")).toBeNull();
+ expect(screen.queryByTestId("server-feedback-error")).toBeNull();
+ });
+
+ test("Error with non-existing fields is not displayed", async () => {
+ overrideOnce("post", "/v1/api/polling_stations/1/data_entries/1", 200, {
+ saved: true,
+ message: "Data entry saved successfully",
+ validation_results: {
+ errors: [
+ {
+ fields: [
+ "data.not_a_real_object.not_a_real_field",
+ "data.not_a_real_object.this_field_does_not_exist",
+ ],
+ code: "NotARealError",
+ },
+ ],
+ warnings: [],
},
});
- const result = await screen.findByTestId("result");
- expect(result).toHaveTextContent(/^Success$/);
+ const user = userEvent.setup();
+
+ render(Component);
+
+ // Since the component does not allow to input values for non-existing fields,
+ // not inputting any values and just clicking the submit button.
+ const submitButton = screen.getByRole("button", { name: "Volgende" });
+ await user.click(submitButton);
+
+ expect(screen.queryByTestId("result")).toBeNull();
+ expect(screen.queryByTestId("feedback-error")).toBeNull();
+ expect(screen.queryByTestId("feedback-warning")).toBeNull();
+ expect(screen.queryByTestId("feedback-server-error")).toBeNull();
});
});
- test("422 response results in display of error message", async () => {
- overrideOnce("post", "/v1/api/polling_stations/1/data_entries/1", 422, {
- message: "422 error from mock",
- });
+ describe("VotersAndVotesForm warnings", () => {
+ test("W.21 AboveThreshold blank votes", async () => {
+ overrideOnce("post", "/v1/api/polling_stations/1/data_entries/1", 200, {
+ saved: true,
+ message: "Data entry saved successfully",
+ validation_results: {
+ errors: [],
+ warnings: [
+ {
+ fields: [
+ "data.votes_counts.blank_votes_count",
+ "data.votes_counts.total_votes_cast_count",
+ ],
+ code: "AboveThreshold",
+ },
+ ],
+ },
+ });
- const user = userEvent.setup();
+ const user = userEvent.setup();
- render(
);
+ render(Component);
- const submitButton = screen.getByRole("button", { name: "Volgende" });
- await user.click(submitButton);
- const result = await screen.findByTestId("result");
- expect(result).toHaveTextContent(/^422 error from mock$/);
- });
+ await user.type(screen.getByTestId("votes_candidates_counts"), "0");
+ await user.type(screen.getByTestId("blank_votes_count"), "1");
+ await user.type(screen.getByTestId("invalid_votes_count"), "0");
+ await user.type(screen.getByTestId("total_votes_cast_count"), "1");
+
+ const submitButton = screen.getByRole("button", { name: "Volgende" });
+ await user.click(submitButton);
- test("500 response results in display of error message", async () => {
- overrideOnce("post", "/v1/api/polling_stations/1/data_entries/1", 500, {
- message: "500 error from mock",
- errorCode: "500_ERROR",
+ const feedbackWarning = await screen.findByTestId("feedback-warning");
+ expect(feedbackWarning).toHaveTextContent(/^AboveThreshold$/);
+ expect(screen.queryByTestId("feedback-server-error")).toBeNull();
+ expect(screen.queryByTestId("feedback-error")).toBeNull();
});
- const user = userEvent.setup();
+ test("W.22 AboveThreshold invalid votes", async () => {
+ overrideOnce("post", "/v1/api/polling_stations/1/data_entries/1", 200, {
+ saved: true,
+ message: "Data entry saved successfully",
+ validation_results: {
+ errors: [],
+ warnings: [
+ {
+ fields: [
+ "data.votes_counts.blank_votes_count",
+ "data.votes_counts.total_votes_cast_count",
+ ],
+ code: "AboveThreshold",
+ },
+ ],
+ },
+ });
- render(
);
+ const user = userEvent.setup();
- const submitButton = screen.getByRole("button", { name: "Volgende" });
- await user.click(submitButton);
- const result = await screen.findByTestId("result");
- expect(result).toHaveTextContent(/^500 error from mock$/);
- });
+ render(Component);
+
+ await user.type(screen.getByTestId("votes_candidates_counts"), "0");
+ await user.type(screen.getByTestId("blank_votes_count"), "0");
+ await user.type(screen.getByTestId("invalid_votes_count"), "1");
+ await user.type(screen.getByTestId("total_votes_cast_count"), "1");
+
+ const submitButton = screen.getByRole("button", { name: "Volgende" });
+ await user.click(submitButton);
- test("Incorrect total is caught by validation", async () => {
- const { getByTestId } = render(
);
+ const feedbackWarning = await screen.findByTestId("feedback-warning");
+ expect(feedbackWarning).toHaveTextContent(/^AboveThreshold$/);
+ expect(screen.queryByTestId("feedback-server-error")).toBeNull();
+ expect(screen.queryByTestId("feedback-error")).toBeNull();
+ });
- const setValue = (id: string, value: string | number) => {
- const el = getByTestId(id);
- fireEvent.change(el, {
- target: { value: `${value}` },
+ test("W.27 EqualInput voters and votes", async () => {
+ overrideOnce("post", "/v1/api/polling_stations/1/data_entries/1", 200, {
+ saved: true,
+ message: "Data entry saved successfully",
+ validation_results: {
+ errors: [
+ {
+ fields: ["data.votes_counts.votes_candidates_counts", "data.political_group_votes"],
+ code: "IncorrectTotal",
+ },
+ ],
+ warnings: [
+ {
+ fields: ["data.voters_counts", "data.votes_counts"],
+ code: "EqualInput",
+ },
+ ],
+ },
});
- };
- setValue("poll_card_count", 1);
- setValue("proxy_certificate_count", 1);
- setValue("voter_card_count", 1);
- setValue("total_admitted_voters_count", 4);
+ const user = userEvent.setup();
+
+ render(Component);
- setValue("votes_candidates_counts", 1);
- setValue("blank_votes_count", 1);
- setValue("invalid_votes_count", 1);
- setValue("total_votes_cast_count", 4);
+ await user.type(screen.getByTestId("poll_card_count"), "1");
+ await user.type(screen.getByTestId("proxy_certificate_count"), "0");
+ await user.type(screen.getByTestId("voter_card_count"), "0");
+ await user.type(screen.getByTestId("total_admitted_voters_count"), "1");
- const user = userEvent.setup();
- const submitButton = screen.getByRole("button", { name: "Volgende" });
- await user.click(submitButton);
+ await user.type(screen.getByTestId("votes_candidates_counts"), "1");
+ await user.type(screen.getByTestId("blank_votes_count"), "0");
+ await user.type(screen.getByTestId("invalid_votes_count"), "0");
+ await user.type(screen.getByTestId("total_votes_cast_count"), "1");
- const result = await screen.findByTestId("error-codes");
- expect(result).toHaveTextContent(/^IncorrectTotal,IncorrectTotal$/);
+ const submitButton = screen.getByRole("button", { name: "Volgende" });
+ await user.click(submitButton);
+
+ const feedbackWarning = await screen.findByTestId("feedback-warning");
+ expect(feedbackWarning).toHaveTextContent(/^EqualInput$/);
+ expect(screen.queryByTestId("feedback-server-error")).toBeNull();
+ expect(screen.queryByTestId("feedback-error")).toBeNull();
+ });
});
});
diff --git a/frontend/app/component/form/voters_and_votes/VotersAndVotesForm.tsx b/frontend/app/component/form/voters_and_votes/VotersAndVotesForm.tsx
index 4bc172831..af221036b 100644
--- a/frontend/app/component/form/voters_and_votes/VotersAndVotesForm.tsx
+++ b/frontend/app/component/form/voters_and_votes/VotersAndVotesForm.tsx
@@ -1,12 +1,9 @@
import * as React from "react";
-import { usePollingStationDataEntry, ValidationResult, ErrorsAndWarnings } from "@kiesraad/api";
+import { useVotersAndVotes, VotersAndVotesValues, useErrorsAndWarnings } from "@kiesraad/api";
import { Button, InputGrid, Feedback, BottomBar, InputGridRow, useTooltip } from "@kiesraad/ui";
-import {
- usePositiveNumberInputMask,
- usePreventFormEnterSubmit,
- fieldNameFromPath,
-} from "@kiesraad/util";
+import { usePositiveNumberInputMask, usePreventFormEnterSubmit } from "@kiesraad/util";
+import { useBlocker } from "react-router-dom";
interface FormElements extends HTMLFormControlsCollection {
poll_card_count: HTMLInputElement;
@@ -33,21 +30,25 @@ export function VotersAndVotesForm() {
} = usePositiveNumberInputMask();
const formRef = React.useRef
(null);
usePreventFormEnterSubmit(formRef);
- const [doSubmit, { data, loading, error }] = usePollingStationDataEntry({
- polling_station_id: 1,
- entry_number: 1,
- });
+
+ const {
+ sectionValues,
+ setSectionValues,
+ loading,
+ errors,
+ warnings,
+ serverError,
+ isCalled,
+ setTemporaryCache,
+ } = useVotersAndVotes();
useTooltip({
onDismiss: resetWarnings,
});
- function handleSubmit(event: React.FormEvent) {
- event.preventDefault();
- const elements = event.currentTarget.elements;
-
- doSubmit({
- data: {
+ const getValues = React.useCallback(
+ (elements: VotersAndVotesFormElement["elements"]): VotersAndVotesValues => {
+ return {
voters_counts: {
poll_card_count: deformat(elements.poll_card_count.value),
proxy_certificate_count: deformat(elements.proxy_certificate_count.value),
@@ -60,95 +61,58 @@ export function VotersAndVotesForm() {
invalid_votes_count: deformat(elements.invalid_votes_count.value),
total_votes_cast_count: deformat(elements.total_votes_cast_count.value),
},
- differences_counts: {
- more_ballots_count: 0,
- fewer_ballots_count: 0,
- unreturned_ballots_count: 0,
- too_few_ballots_handed_out_count: 0,
- too_many_ballots_handed_out_count: 0,
- other_explanation_count: 0,
- no_explanation_count: 0,
- },
- political_group_votes: [
- {
- candidate_votes: [{ number: 1, votes: 0 }],
- number: 1,
- total: 0,
- },
- ],
- },
- });
- }
-
- const errorsAndWarnings: Map = React.useMemo(() => {
- const result = new Map();
+ };
+ },
+ [deformat],
+ );
- const process = (target: keyof ErrorsAndWarnings, arr: ValidationResult[]) => {
- arr.forEach((v) => {
- v.fields.forEach((f) => {
- const fieldName = fieldNameFromPath(f);
- if (!result.has(fieldName)) {
- result.set(fieldName, { errors: [], warnings: [] });
- }
- const field = result.get(fieldName);
- if (field) {
- field[target].push({
- code: v.code,
- id: fieldName,
- });
- }
- });
+ function handleSubmit(event: React.FormEvent) {
+ event.preventDefault();
+ const elements = event.currentTarget.elements;
+ setSectionValues(getValues(elements));
+ }
+ //const blocker = useBlocker() use const blocker to render confirmation UI.
+ useBlocker(() => {
+ if (formRef.current && !isCalled) {
+ const elements = formRef.current.elements as VotersAndVotesFormElement["elements"];
+ const values = getValues(elements);
+ setTemporaryCache({
+ key: "voters_and_votes",
+ data: values,
});
- };
-
- if (data && data.validation_results.errors.length > 0) {
- process("errors", data.validation_results.errors);
- }
- if (data && data.validation_results.warnings.length > 0) {
- process("warnings", data.validation_results.warnings);
}
+ return false;
+ });
- inputMaskWarnings.forEach((warning) => {
- if (!result.has(warning.id)) {
- result.set(warning.id, { errors: [], warnings: [] });
- }
- const field = result.get(warning.id);
- if (field) {
- field.warnings.push(warning);
- }
- });
-
- return result;
- }, [data, inputMaskWarnings]);
+ const errorsAndWarnings = useErrorsAndWarnings(errors, warnings, inputMaskWarnings);
React.useEffect(() => {
- if (data) {
+ if (isCalled) {
window.scrollTo(0, 0);
}
- }, [data]);
-
- const hasValidationError = data && data.validation_results.errors.length > 0;
- const hasValidationWarning = data && data.validation_results.warnings.length > 0;
+ }, [isCalled]);
+ const hasValidationError = errors.length > 0;
+ const hasValidationWarning = warnings.length > 0;
+ const success = isCalled && !hasValidationError && !hasValidationWarning && !loading;
return (