Adds a segmented Monthly/YTD toggle next to the reference-month picker that flips the four KPI cards (income, expenses, net, savings rate) between the reference-month value (unchanged default) and a Year-to-Date cumulative view. In YTD mode, the "current" value sums January to the reference month of the reference year; MoM delta compares it to Jan to (refMonth - 1) of the same year (null in January, since no prior YTD window exists); YoY delta compares it to Jan to refMonth of the previous year; savings rate is recomputed from YTD income and expenses, and stays null when YTD income is zero. The 13-month sparkline, top movers, seasonality and budget adherence cards remain monthly regardless of the toggle (by design). The savings-rate tooltip is now dynamic and mirrors the active mode. The mode is persisted in localStorage under `reports-cartes-period-mode`. Also adds a dedicated Cartes section to `docs/guide-utilisateur.md` covering the four KPI formulas, the Monthly/YTD toggle and its effect on deltas, the sparkline, top movers, seasonality, budget adherence and the savings-rate edge case. Mirrored in the in-app `docs.reports` i18n tree (features/steps/ tips extended) for both FR and EN. No SQL migration: YTD sums are computed from the already-fetched `flowByMonth` map, so no extra round trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
77 lines
2.2 KiB
TypeScript
77 lines
2.2 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
import {
|
|
defaultCartesReferencePeriod,
|
|
readCartesPeriodMode,
|
|
CARTES_PERIOD_MODE_STORAGE_KEY,
|
|
} from "./useCartes";
|
|
|
|
describe("defaultCartesReferencePeriod", () => {
|
|
it("returns the month before the given date", () => {
|
|
expect(defaultCartesReferencePeriod(new Date(2026, 3, 15))).toEqual({
|
|
year: 2026,
|
|
month: 3,
|
|
});
|
|
});
|
|
|
|
it("wraps around January to December of the previous year", () => {
|
|
expect(defaultCartesReferencePeriod(new Date(2026, 0, 10))).toEqual({
|
|
year: 2025,
|
|
month: 12,
|
|
});
|
|
});
|
|
|
|
it("handles the last day of a month", () => {
|
|
expect(defaultCartesReferencePeriod(new Date(2026, 5, 30))).toEqual({
|
|
year: 2026,
|
|
month: 5,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("readCartesPeriodMode (localStorage round-trip)", () => {
|
|
const store = new Map<string, string>();
|
|
const mockLocalStorage = {
|
|
getItem: vi.fn((key: string) => store.get(key) ?? null),
|
|
setItem: vi.fn((key: string, value: string) => {
|
|
store.set(key, value);
|
|
}),
|
|
removeItem: vi.fn((key: string) => {
|
|
store.delete(key);
|
|
}),
|
|
clear: vi.fn(() => store.clear()),
|
|
key: vi.fn(),
|
|
length: 0,
|
|
};
|
|
|
|
beforeEach(() => {
|
|
store.clear();
|
|
vi.stubGlobal("localStorage", mockLocalStorage);
|
|
});
|
|
|
|
it("defaults to 'month' when nothing is persisted", () => {
|
|
expect(readCartesPeriodMode()).toBe("month");
|
|
});
|
|
|
|
it("reads back a previously persisted 'month' value (round-trip)", () => {
|
|
store.set(CARTES_PERIOD_MODE_STORAGE_KEY, "month");
|
|
expect(readCartesPeriodMode()).toBe("month");
|
|
});
|
|
|
|
it("reads back a previously persisted 'ytd' value (round-trip)", () => {
|
|
store.set(CARTES_PERIOD_MODE_STORAGE_KEY, "ytd");
|
|
expect(readCartesPeriodMode()).toBe("ytd");
|
|
});
|
|
|
|
it("falls back to default for unknown/corrupted values", () => {
|
|
store.set(CARTES_PERIOD_MODE_STORAGE_KEY, "bogus");
|
|
expect(readCartesPeriodMode()).toBe("month");
|
|
});
|
|
|
|
it("supports a custom fallback", () => {
|
|
expect(readCartesPeriodMode(CARTES_PERIOD_MODE_STORAGE_KEY, "ytd")).toBe("ytd");
|
|
});
|
|
|
|
it("uses the expected storage key", () => {
|
|
expect(CARTES_PERIOD_MODE_STORAGE_KEY).toBe("reports-cartes-period-mode");
|
|
});
|
|
});
|