fix: T4 browser-check findings, interval on season state, T6 plan refresh
- Honey batch create omits an empty batch number (was 400); product form
labels the legacy total weight, detail shows jar weight and shelf life;
bottling hints next to their fields, values cleared on product change,
post-save water-content wording; withdraw reason placeholder not
selectable; honey batch actions offline-disabled with reason, 44 px.
- Offline signal: one slow request no longer flips the app offline; two
consecutive timeouts do, any HTTP answer clears it, aborts never count.
- Phenology sheet as a side card on desktop with focus return; confirm
dialogs as alertdialog; step-up texts in du-form ("Das Passwort stimmt
nicht."); backup import gives its disabled reason; missing event labels
plus a guard test; profile buttons 44 px.
- GET /phenology/state returns intervalDays via the shared
inspectionIntervalOf (autopilot, digest, season state use one source).
- T6 plan aligned with the code after T4, plus owner decisions of
2026-10-15 (Berlin day for restrictions, COMPLIANCE category, override
without step-up).
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
9760ce154f
commit
7a1480138d
63 files changed
+1076
-251
No files matched your search
@@ -29,7 +29,7 @@ describe("StepUpDialog", () => {
|
||||
const confirm = screen.getByRole("button", { name: "Verbinden" });
|
||||
expect(confirm).toBeDisabled();
|
||||
|
||||
await userEvent.type(screen.getByLabelText("Ihr Passwort"), "geheim");
|
||||
await userEvent.type(screen.getByLabelText("Dein Passwort"), "geheim");
|
||||
await userEvent.click(confirm);
|
||||
|
||||
await waitFor(() => expect(onConfirm).toHaveBeenCalledWith({ password: "geheim" }));
|
||||
@@ -39,7 +39,7 @@ describe("StepUpDialog", () => {
|
||||
it("asks for the six-digit code with MFA and sends both", async () => {
|
||||
const { onConfirm } = renderDialog({ needsMfa: true });
|
||||
|
||||
await userEvent.type(screen.getByLabelText("Ihr Passwort"), "geheim");
|
||||
await userEvent.type(screen.getByLabelText("Dein Passwort"), "geheim");
|
||||
const confirm = screen.getByRole("button", { name: "Verbinden" });
|
||||
await userEvent.type(screen.getByLabelText("Code aus der Authenticator-App"), "12ab34");
|
||||
expect(confirm).toBeDisabled();
|
||||
@@ -56,21 +56,21 @@ describe("StepUpDialog", () => {
|
||||
const onConfirm = vi.fn().mockRejectedValue(new ApiError(401, "Das Passwort ist falsch."));
|
||||
const { onOpenChange } = renderDialog({ onConfirm });
|
||||
|
||||
await userEvent.type(screen.getByLabelText("Ihr Passwort"), "falsch");
|
||||
await userEvent.type(screen.getByLabelText("Dein Passwort"), "falsch");
|
||||
await userEvent.click(screen.getByRole("button", { name: "Verbinden" }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole("alert")).toHaveTextContent("Das Passwort ist falsch."),
|
||||
);
|
||||
expect(onOpenChange).not.toHaveBeenCalledWith(false);
|
||||
expect(screen.getByLabelText("Ihr Passwort")).toHaveValue("");
|
||||
expect(screen.getByLabelText("Ihr Passwort")).toHaveFocus();
|
||||
expect(screen.getByLabelText("Dein Passwort")).toHaveValue("");
|
||||
expect(screen.getByLabelText("Dein Passwort")).toHaveFocus();
|
||||
});
|
||||
|
||||
it("shows the German too-many-attempts text on 429", async () => {
|
||||
renderDialog({ onConfirm: vi.fn().mockRejectedValue(new ApiError(429, "ThrottlerException")) });
|
||||
|
||||
await userEvent.type(screen.getByLabelText("Ihr Passwort"), "x");
|
||||
await userEvent.type(screen.getByLabelText("Dein Passwort"), "x");
|
||||
await userEvent.click(screen.getByRole("button", { name: "Verbinden" }));
|
||||
|
||||
await waitFor(() =>
|
||||
@@ -83,7 +83,7 @@ describe("StepUpDialog", () => {
|
||||
it("says the session expired on a bare 401", async () => {
|
||||
renderDialog({ onConfirm: vi.fn().mockRejectedValue(new ApiError(401, "Unauthorized")) });
|
||||
|
||||
await userEvent.type(screen.getByLabelText("Ihr Passwort"), "x");
|
||||
await userEvent.type(screen.getByLabelText("Dein Passwort"), "x");
|
||||
await userEvent.click(screen.getByRole("button", { name: "Verbinden" }));
|
||||
|
||||
await waitFor(() =>
|
||||
@@ -96,17 +96,17 @@ describe("StepUpDialog", () => {
|
||||
it("makes the inputs read-only while the action runs", async () => {
|
||||
renderDialog({ onConfirm: vi.fn(() => new Promise<void>(() => {})) });
|
||||
|
||||
await userEvent.type(screen.getByLabelText("Ihr Passwort"), "geheim");
|
||||
await userEvent.type(screen.getByLabelText("Dein Passwort"), "geheim");
|
||||
await userEvent.click(screen.getByRole("button", { name: "Verbinden" }));
|
||||
|
||||
await waitFor(() => expect(screen.getByLabelText("Ihr Passwort")).toHaveAttribute("readonly"));
|
||||
await waitFor(() => expect(screen.getByLabelText("Dein Passwort")).toHaveAttribute("readonly"));
|
||||
});
|
||||
|
||||
it("cannot be closed with Escape while the action runs, and shows the pending label", async () => {
|
||||
const onConfirm = vi.fn(() => new Promise<void>(() => {}));
|
||||
const { onOpenChange } = renderDialog({ onConfirm, pendingLabel: "Wird eingespielt…" });
|
||||
|
||||
await userEvent.type(screen.getByLabelText("Ihr Passwort"), "geheim");
|
||||
await userEvent.type(screen.getByLabelText("Dein Passwort"), "geheim");
|
||||
await userEvent.click(screen.getByRole("button", { name: "Verbinden" }));
|
||||
expect(await screen.findByRole("button", { name: "Wird eingespielt…" })).toBeDisabled();
|
||||
|
||||
|
||||
Reference in new issue
Block a user