Wer nach dem Gehaltseingang plant, stellt unter Einstellungen den Tag ein, ab dem ein neuer Monat zählt. Der Zeitraum läuft dann vom Gehaltstag bis zum Vortag des nächsten und trägt den Namen des Monats, in dem er beginnt: Mit dem 25. umfasst „September 2026“ den 25.09. bis zum 24.10. Ein Starttag jenseits der Monatslänge rutscht auf den Monatsletzten, sodass 31 verlässlich den letzten Tag des Monats meint. Dashboard, Cashflow-Kalender, Budgets, Zwölf-Monats-Vorschau, die Kategorienauswertung, der Monatsexport und die Benachrichtigung über überschrittene Budgets rechnen mit diesem Zeitraum. Budgets bleiben je Monat gepflegt; der Bezeichner ist weiterhin der Monatserste, nur der Schnitt verschiebt sich. Bestandsinstallationen bleiben beim Ersten. Die Einstellung liegt in einer einzeiligen Tabelle hinter GET/PUT /api/settings; die Monatsauswertungen liefern zusätzlich period_start und period_end. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fj7mB1PGA1aDHyfdSGHgzD
84 lines
2.8 KiB
TypeScript
84 lines
2.8 KiB
TypeScript
/** Test des Abschnitts „Monatsbeginn“ in den Einstellungen. */
|
||
|
||
import { screen, waitFor } from "@testing-library/react";
|
||
import userEvent from "@testing-library/user-event";
|
||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||
|
||
import { SettingsPage } from "@/pages/SettingsPage";
|
||
import { renderWithProviders } from "@/test/utils";
|
||
|
||
function mockApi() {
|
||
const anfragen: { url: string; method: string; body: unknown }[] = [];
|
||
let monatsbeginn = 1;
|
||
|
||
const json = (daten: unknown) =>
|
||
new Response(JSON.stringify(daten), {
|
||
status: 200,
|
||
headers: { "content-type": "application/json" },
|
||
});
|
||
|
||
vi.stubGlobal(
|
||
"fetch",
|
||
vi.fn(async (eingabe: RequestInfo | URL, init?: RequestInit) => {
|
||
const url = typeof eingabe === "string" ? eingabe : eingabe.toString();
|
||
const body = typeof init?.body === "string" ? JSON.parse(init.body) : null;
|
||
anfragen.push({ url, method: init?.method ?? "GET", body });
|
||
|
||
if (url.includes("/api/settings")) {
|
||
if (init?.method === "PUT") monatsbeginn = body.month_start_day;
|
||
return json({
|
||
month_start_day: monatsbeginn,
|
||
current_month: "2026-09-01",
|
||
current_period_start: "2026-09-01",
|
||
current_period_end: "2026-09-30",
|
||
});
|
||
}
|
||
if (url.includes("/api/accounts")) return json([]);
|
||
return json({});
|
||
}),
|
||
);
|
||
|
||
return anfragen;
|
||
}
|
||
|
||
describe("Einstellungen: Monatsbeginn", () => {
|
||
beforeEach(() => {
|
||
vi.useFakeTimers({ shouldAdvanceTime: true });
|
||
vi.setSystemTime(new Date(2026, 8, 10, 12));
|
||
});
|
||
|
||
afterEach(() => {
|
||
vi.useRealTimers();
|
||
});
|
||
|
||
it("zeigt die Zeiträume zur Auswahl und speichert den Gehaltstag", async () => {
|
||
const nutzer = userEvent.setup();
|
||
const anfragen = mockApi();
|
||
renderWithProviders(<SettingsPage />);
|
||
|
||
await nutzer.click(screen.getByRole("tab", { name: "Monatsbeginn" }));
|
||
|
||
const auswahl = await screen.findByLabelText("Monatsbeginn");
|
||
expect(auswahl).toHaveValue("1");
|
||
// Vorgabe: der Zeitraum deckt sich mit dem Kalendermonat.
|
||
expect(screen.getByText("01.09.2026 – 30.09.2026")).toBeInTheDocument();
|
||
|
||
await nutzer.selectOptions(auswahl, "25");
|
||
// Die Vorschau rechnet sofort, noch vor dem Speichern.
|
||
expect(screen.getByText("25.08.2026 – 24.09.2026")).toBeInTheDocument();
|
||
expect(screen.getByText("25.09.2026 – 24.10.2026")).toBeInTheDocument();
|
||
|
||
await nutzer.click(screen.getByRole("button", { name: "Speichern" }));
|
||
|
||
await waitFor(() => {
|
||
expect(
|
||
anfragen.some(
|
||
(anfrage) => anfrage.method === "PUT" && anfrage.url.includes("/api/settings"),
|
||
),
|
||
).toBe(true);
|
||
});
|
||
const gespeichert = anfragen.find((anfrage) => anfrage.method === "PUT");
|
||
expect(gespeichert?.body).toEqual({ month_start_day: 25 });
|
||
});
|
||
});
|