Skip to content
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

Frontend: use API to show list of polling stations on polling station select page #146

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
03ce3dc
Added backend call to list polling stations
cikzh Jul 3, 2024
91eab26
Mapped stations to table rows
cikzh Jul 3, 2024
2b5585a
Created basic polling station selector
cikzh Jul 10, 2024
0222412
Added polling station provider
cikzh Jul 10, 2024
7b76790
Added polling station mock data
cikzh Jul 17, 2024
23b4afc
Removed design placeholder
cikzh Jul 17, 2024
4a3e250
Added error tekst to input element
cikzh Jul 17, 2024
8fcda1d
Added test for polling station list
cikzh Jul 18, 2024
79cf161
Added styling for loader
cikzh Jul 18, 2024
5a39503
Added styling for selector feedback
cikzh Jul 18, 2024
5e38fb4
Improved loader styling in polling station list
cikzh Jul 18, 2024
f322c91
Update frontend/app/component/form/polling_station_choice/PollingStat…
cikzh Jul 18, 2024
7bbf69e
Removed validation errors
cikzh Jul 18, 2024
19c2c3a
Update frontend/app/component/form/polling_station_choice/PollingStat…
cikzh Jul 18, 2024
f3f2037
Added tests for the selector
cikzh Jul 18, 2024
0c14009
Added debounce to selector input
cikzh Jul 22, 2024
8e8f1e8
Wrapped the callback in a ref
cikzh Jul 24, 2024
0490065
Fixed icon padding
cikzh Jul 24, 2024
189bd36
Fixed formatting
cikzh Jul 24, 2024
bf831b7
Now preventing navigation to non-existing polling stations
cikzh Jul 24, 2024
5188078
Now handling the correct input events for submitting
cikzh Jul 24, 2024
9b07fc7
Added error messages when no polling stations are found
cikzh Jul 24, 2024
93c5df9
Added test for empty polling stations alert
cikzh Jul 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,112 @@ import { userEvent } from "@testing-library/user-event";
import { describe, expect, test } from "vitest";

import { PollingStationChoiceForm } from "app/component/form/polling_station_choice/PollingStationChoiceForm.tsx";
import { render, screen } from "app/test/unit";
import { render, screen, within } from "app/test/unit";

import { PollingStationProvider, PollingStationsContext } from "@kiesraad/api";

describe("Test PollingStationChoiceForm", () => {
test("Form field entry and buttons", async () => {
test("Form field entry", async () => {
const user = userEvent.setup();

render(<PollingStationChoiceForm />);
render(
<PollingStationProvider electionId={1}>
<PollingStationChoiceForm />
</PollingStationProvider>,
);

const pollingStation = screen.getByTestId("pollingStation");
const submitButton = screen.getByRole("button", { name: "Beginnen" });

// Test if pattern on field works
// Test if the feedback field shows an error
await user.type(pollingStation, "abc");
await user.click(submitButton);
expect(pollingStation).toBeInvalid();
const pollingStationFeedback = await screen.findByTestId("pollingStationSelectorFeedback");
expect(
within(pollingStationFeedback).getByText("Geen stembureau gevonden met nummer abc"),
).toBeVisible();

await user.clear(pollingStation);

// Test if maxLength on field works
await user.type(pollingStation, "1234567");
expect(pollingStation).toHaveValue("123456");

expect(screen.getByText("Kies het stembureau")).not.toBeVisible();
await user.clear(pollingStation);
});

test("Selecting a valid polling station", async () => {
const user = userEvent.setup();
render(
<PollingStationProvider electionId={1}>
<PollingStationChoiceForm />
</PollingStationProvider>,
);
const pollingStation = screen.getByTestId("pollingStation");

// Test if the polling station name is shown
await user.type(pollingStation, "20");
const pollingStationFeedback = await screen.findByTestId("pollingStationSelectorFeedback");
expect(within(pollingStationFeedback).getByText('Stembureau "Op Rolletjes"')).toBeVisible();
});

test("Selecting a non-existing polling station", async () => {
const user = userEvent.setup();
render(
<PollingStationProvider electionId={1}>
<PollingStationChoiceForm />
</PollingStationProvider>,
);
const pollingStation = screen.getByTestId("pollingStation");

// Test if the polling station name is shown
await user.type(pollingStation, "99");
const pollingStationFeedback = await screen.findByTestId("pollingStationSelectorFeedback");
expect(
within(pollingStationFeedback).getByText("Geen stembureau gevonden met nummer 99"),
).toBeVisible();
});

test("Polling station list", async () => {
const user = userEvent.setup();

render(
<PollingStationProvider electionId={1}>
<PollingStationChoiceForm />
</PollingStationProvider>,
);

expect(screen.getByText("Kies het stembureau")).not.toBeVisible();
const openPollingStationList = screen.getByTestId("openPollingStationList");
await user.click(openPollingStationList);

expect(screen.getByText("Kies het stembureau")).toBeVisible();

await user.click(submitButton);
// Check if the station number and name exist and are visible
const pollingStationList = screen.getByTestId("polling_station_list");
expect(within(pollingStationList).getByText("20")).toBeVisible();
expect(within(pollingStationList).getByText('Stembureau "Op Rolletjes"')).toBeVisible();
expect(within(pollingStationList).getByText("21")).toBeVisible();
expect(within(pollingStationList).getByText("Testplek")).toBeVisible();
});

test("Polling station list no stations", async () => {
const user = userEvent.setup();

render(
<PollingStationsContext.Provider
value={{
pollingStationsLoading: false,
pollingStations: [],
}}
>
<PollingStationChoiceForm />
</PollingStationsContext.Provider>,
);

const openPollingStationList = screen.getByTestId("openPollingStationList");
await user.click(openPollingStationList);
expect(screen.getByText("Kies het stembureau")).toBeVisible();

// Check if the error message is visible
expect(screen.getByText("Geen stembureaus gevonden")).toBeVisible();
});
});
Original file line number Diff line number Diff line change
@@ -1,46 +1,51 @@
import { useContext, useState } from "react";
import { useNavigate } from "react-router-dom";

import { IconChevronRight } from "@kiesraad/icon";
import { Badge, BottomBar, Button, InputField } from "@kiesraad/ui";
import { PollingStationsContext } from "@kiesraad/api";
import { Alert, BottomBar, Button, Icon, Spinner } from "@kiesraad/ui";

interface FormElements extends HTMLFormControlsCollection {
number: HTMLInputElement;
}

interface PollingStationChoiceFormElement extends HTMLFormElement {
readonly elements: FormElements;
}
import { PollingStationSelector } from "./PollingStationSelector";
import { PollingStationsList } from "./PollingStationsList";

export function PollingStationChoiceForm() {
const navigate = useNavigate();
const handleRowClick = () => {
navigate(`./030/recounted`);

const { pollingStations, pollingStationsLoading } = useContext(PollingStationsContext);
const [pollingStationNumber, setPollingStationNumber] = useState<string>("");

const handleSubmit = () => {
const parsedStationNumber = parseInt(pollingStationNumber, 10);
if (pollingStations.some((ps) => ps.number === parsedStationNumber)) {
navigate(`./${pollingStationNumber}/recounted`);
}
};
function handleSubmit(event: React.FormEvent<PollingStationChoiceFormElement>) {
event.preventDefault();
navigate("./030/recounted");
}

return (
<form onSubmit={handleSubmit}>
<form
onSubmit={(e) => {
e.preventDefault();
return;
}}
>
<h2 className="form_title">Welk stembureau ga je invoeren?</h2>
<InputField
id="pollingStation"
name="number"
label="Voer het nummer in:"
fieldWidth="narrow"
margin={false}
pattern="\d+"
title="Alleen positieve nummers toegestaan"
maxLength={6}
<PollingStationSelector
pollingStationNumber={pollingStationNumber}
setPollingStationNumber={setPollingStationNumber}
handleSubmit={handleSubmit}
/>
<p className="md">
Klopt de naam van het stembureau met de naam op je papieren proces verbaal?
<br />
Dan kan je beginnen.
</p>
<BottomBar type="form">
<Button type="submit" size="lg">
<Button
type="button"
size="lg"
onClick={() => {
handleSubmit();
}}
>
Beginnen
</Button>
<span className="button_hint">SHIFT + Enter</span>
Expand All @@ -56,115 +61,20 @@ export function PollingStationChoiceForm() {
</p>
</summary>
<h2 className="form_title table_title">Kies het stembureau</h2>
<table id="polling_station_list" className="overview_table">
<thead>
<tr>
<th className="align-center">Nummer</th>
<th>Stembureau</th>
<th></th>
</tr>
</thead>
<tbody>
<tr onClick={handleRowClick}>
<td width="6.5rem" className="number">
1
</td>
<td>
<span>Nachthemelstraat 21</span>
<Badge type="first_entry" />
</td>
<td width="5rem">
<div className="link">
<IconChevronRight />
</div>
</td>
</tr>
<tr onClick={handleRowClick}>
<td width="6.5rem" className="number">
2
</td>
<td>
<span>Schoolstraat 78</span>
<Badge type="second_entry" />
</td>
<td width="5rem">
<div className="link">
<IconChevronRight />
</div>
</td>
</tr>
<tr onClick={handleRowClick}>
<td width="6.5rem" className="number">
3
</td>
<td>
<span>Fluisterbosdreef 8</span>
<Badge type="extra_entry" />
</td>
<td width="5rem">
<div className="link">
<IconChevronRight />
</div>
</td>
</tr>
<tr onClick={handleRowClick}>
<td width="6.5rem" className="number">
4
</td>
<td>
<span>Wilhelminastraat 21</span>
<Badge type="objections" />
</td>
<td width="5rem">
<div className="link">
<IconChevronRight />
</div>
</td>
</tr>
<tr onClick={handleRowClick}>
<td width="6.5rem" className="number">
5
</td>
<td>
<span>Tuinstraat 2</span>
<Badge type="difference" />
</td>
<td width="5rem">
<div className="link">
<IconChevronRight />
</div>
</td>
</tr>
<tr onClick={handleRowClick}>
<td width="6.5rem" className="number">
6
</td>
<td>
<span>Rietland 31</span>
<Badge type="correction" />
</td>
<td width="5rem">
<div className="link">
<IconChevronRight />
</div>
</td>
</tr>
<tr onClick={handleRowClick}>
<td width="6.5rem" className="number">
7
</td>
<td>
<span>Grote Markt 1</span>
<Badge type="definitive" />
</td>
<td width="5rem">
<div className="link">
<IconChevronRight />
</div>
</td>
</tr>
</tbody>
</table>
{(() => {
if (pollingStationsLoading) {
return (
<div className="flex">
<Icon icon={<Spinner size="lg" />} />
aan het zoeken …
</div>
);
} else if (pollingStations.length === 0) {
return <Alert type={"error"}>Geen stembureaus gevonden</Alert>;
} else {
return <PollingStationsList pollingStations={pollingStations} />;
}
})()}
</details>
</form>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.container {
display: flex;
align-items: center;
}

.result {
margin: 0 1em;
padding: 0 2em;
background-color: var(--bg-gray-darker);
height: 4em;
width: 100%;
align-content: center;
font-size: var(--font-size-body);
display: flex;
align-items: center;

&.error {
background-color: var(--color-error-bg);
}

&.success {
background-color: var(--color-success-bg);
}

.icon {
padding-top: 6px;
padding-right: 1em;
}
}
Loading