feat(t5): Auswahlgruppe mit Haken, Karten-Variante, Plus/Minus (T5a A6)
- ChoiceGroup nach Spec 10.2: gewählt mit Haken und 2-px-Tintenrahmen, „Nicht geprüft“ gestrichelt; neue Variante appearance="cards" mit Icon und Beschreibung (Winter-Ergebnis, Darstellung, Behördenmeldung). - Eigenbau-Radios in Behördenmeldung, Phänologie-Bericht und Zeilen-Ausrichtung durch ChoiceGroup ersetzt. - Button pressed → aria-pressed (Fett/Kursiv, „Aufbau als Liste“); Druck-Versatz nur mit motion-full. - NumberStepper (ganze Schritte) für Winter-Waben, Milbenzahl (neu und bearbeiten), Weiselzellen, Entnahme/Zählbestand, Startposition; Entnahme nur 1 bis Restmenge, Startposition höchstens 1000. - Baukasten-Wächter: Radios nur in ChoiceGroup, aria-pressed nur in Button und Kalender, auch in Objektform und .ts-Dateien. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
6ac9eef93b
commit
208e9baaf0
30 files changed
+809
-198
No files matched your search
@@ -1,9 +1,10 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { render, screen, within } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { StockCard } from "./stock-card";
|
||||
import { fetchStock } from "@/lib/stock/api";
|
||||
import type { BatchStock } from "@/lib/stock/types";
|
||||
import { correctStock, fetchStock, withdrawStock } from "@/lib/stock/api";
|
||||
import type { BatchStock, StockBatchPath } from "@/lib/stock/types";
|
||||
|
||||
vi.mock("@/lib/stock/api", () => ({
|
||||
fetchStock: vi.fn(),
|
||||
@@ -11,17 +12,17 @@ vi.mock("@/lib/stock/api", () => ({
|
||||
correctStock: vi.fn(),
|
||||
}));
|
||||
|
||||
function renderCard(canBook = true) {
|
||||
function renderCard(canBook = true, unit = "Gläser", path: StockBatchPath = "bottling-batches") {
|
||||
const client = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false } },
|
||||
});
|
||||
return render(
|
||||
<QueryClientProvider client={client}>
|
||||
<StockCard
|
||||
path="bottling-batches"
|
||||
path={path}
|
||||
id="b1"
|
||||
initialLabel="Abgefüllt"
|
||||
unit="Gläser"
|
||||
unit={unit}
|
||||
canBook={canBook}
|
||||
/>
|
||||
</QueryClientProvider>,
|
||||
@@ -107,4 +108,61 @@ describe("StockCard", () => {
|
||||
screen.queryByRole("button", { name: /Inventur/ }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
it("stock card withdraw and counted stock use NumberStepper", async () => {
|
||||
vi.mocked(fetchStock).mockResolvedValue(stock(80));
|
||||
vi.mocked(withdrawStock).mockResolvedValue(undefined as never);
|
||||
vi.mocked(correctStock).mockResolvedValue(undefined as never);
|
||||
renderCard();
|
||||
|
||||
await userEvent.click(await screen.findByRole("button", { name: /Entnehmen/ }));
|
||||
let dialog = await screen.findByRole("dialog");
|
||||
const quantity = within(dialog).getByLabelText("Anzahl (Gläser)");
|
||||
expect(quantity).toHaveValue("1");
|
||||
expect(quantity).toHaveAttribute("inputmode", "numeric");
|
||||
await userEvent.click(within(dialog).getByRole("button", { name: /Eins mehr/ }));
|
||||
await userEvent.click(within(dialog).getByRole("button", { name: "Entnahme buchen" }));
|
||||
expect(vi.mocked(withdrawStock).mock.calls[0][2]).toMatchObject({ quantity: 2 });
|
||||
|
||||
await userEvent.click(await screen.findByRole("button", { name: /Inventur korrigieren/ }));
|
||||
dialog = await screen.findByRole("dialog");
|
||||
const counted = within(dialog).getByLabelText("Gezählt (Gläser)");
|
||||
expect(counted).toHaveValue("80");
|
||||
await userEvent.click(within(dialog).getByRole("button", { name: /Eins weniger/ }));
|
||||
expect(within(dialog).getByText(/Differenz: -1/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps a typed field for amounts in grams", async () => {
|
||||
vi.mocked(fetchStock).mockResolvedValue(stock(500));
|
||||
renderCard(true, "g", "raw-material-lots");
|
||||
|
||||
await userEvent.click(await screen.findByRole("button", { name: /Entnehmen/ }));
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
expect(within(dialog).getByLabelText("Anzahl (g)")).toHaveAttribute("type", "number");
|
||||
expect(within(dialog).queryByRole("button", { name: /mehr$/ })).toBeNull();
|
||||
});
|
||||
|
||||
it("does not book an empty withdrawal", async () => {
|
||||
vi.mocked(fetchStock).mockResolvedValue(stock(80));
|
||||
renderCard();
|
||||
|
||||
await userEvent.click(await screen.findByRole("button", { name: /Entnehmen/ }));
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
await userEvent.clear(within(dialog).getByLabelText("Anzahl (Gläser)"));
|
||||
expect(within(dialog).getByRole("button", { name: "Entnahme buchen" })).toBeDisabled();
|
||||
});
|
||||
it("does not book a withdrawal of 0 or more than is left", async () => {
|
||||
vi.mocked(fetchStock).mockResolvedValue(stock(80));
|
||||
renderCard();
|
||||
|
||||
await userEvent.click(await screen.findByRole("button", { name: /Entnehmen/ }));
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
const quantity = within(dialog).getByLabelText("Anzahl (Gläser)");
|
||||
const book = within(dialog).getByRole("button", { name: "Entnahme buchen" });
|
||||
await userEvent.clear(quantity);
|
||||
await userEvent.type(quantity, "0");
|
||||
expect(book).toBeDisabled();
|
||||
await userEvent.clear(quantity);
|
||||
await userEvent.type(quantity, "80");
|
||||
expect(book).toBeEnabled();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user