Simpl-Resultat/src/hooks/useReportsPeriod.test.ts
le king fu 65be6c7482
All checks were successful
PR Check / rust (pull_request) Successful in 21m28s
PR Check / frontend (pull_request) Successful in 2m22s
feat(reports): extend useReportsPeriod with account filter foundation
Add accountIds: number[] to the shared useReportsPeriod hook, URL-backed
via a dedicated `sources` query param (comma-separated, bookmarkable like
the existing period/from/to). Purely additive: the hook's existing shape
is unchanged, so all 6 consumers (useTrends, useCompare, useCategoryZoom,
ReportsCategoryPage, ReportsPage, ReportsComparePage) keep compiling as-is.

Also adds the shared ReportFilters { period, accountIds } type for
follow-up issues to consume, and exports pure parseAccountIds/
serializeAccountIds helpers (same hookless-testability pattern as
resolveReportsPeriod). URL parsing validates each token as a finite
integer via a strict regex + Number.isSafeInteger, dropping invalid
tokens individually rather than discarding the whole selection.

This is foundation only: no UI control exposes the filter yet, and no
service reads accountIds yet (both land in follow-up issues of the
"rapports uniformes" epic).

Resolves #272

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 15:01:43 -04:00

126 lines
4.3 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { resolveReportsPeriod, parseAccountIds, serializeAccountIds } from "./useReportsPeriod";
describe("resolveReportsPeriod", () => {
const fixedToday = new Date("2026-04-14T12:00:00Z");
it("defaults to current civil year when no URL params are set", () => {
const result = resolveReportsPeriod(null, null, null, fixedToday);
expect(result.from).toBe("2026-01-01");
expect(result.to).toBe("2026-12-31");
expect(result.period).toBe("custom");
});
it("restores state from bookmarked from/to params", () => {
const result = resolveReportsPeriod("2025-03-01", "2025-06-30", null, fixedToday);
expect(result.from).toBe("2025-03-01");
expect(result.to).toBe("2025-06-30");
expect(result.period).toBe("custom");
});
it("keeps period=yearly alongside explicit from/to", () => {
const result = resolveReportsPeriod("2024-01-01", "2024-12-31", "year", fixedToday);
expect(result.period).toBe("year");
});
it("ignores malformed dates and falls back to the civil year", () => {
const result = resolveReportsPeriod("not-a-date", "also-not", null, fixedToday);
expect(result.from).toBe("2026-01-01");
expect(result.to).toBe("2026-12-31");
expect(result.period).toBe("custom");
});
it("resolves preset period values without from/to", () => {
const result = resolveReportsPeriod(null, null, "6months", fixedToday);
expect(result.period).toBe("6months");
expect(result.from).toBeTruthy();
expect(result.to).toBeTruthy();
});
it("rejects an invalid period string and falls back to civil year custom", () => {
const result = resolveReportsPeriod(null, null, "bogus", fixedToday);
expect(result.period).toBe("custom");
expect(result.from).toBe("2026-01-01");
});
it("treats `all` as a preset with empty range (service handles the clauses)", () => {
const result = resolveReportsPeriod(null, null, "all", fixedToday);
expect(result.period).toBe("all");
// Fallback civil year when computeDateRange returns empty
expect(result.from).toBe("2026-01-01");
expect(result.to).toBe("2026-12-31");
});
});
describe("parseAccountIds", () => {
it("defaults to an empty array (no filter) when the sources param is absent", () => {
expect(parseAccountIds(null)).toEqual([]);
});
it("defaults to an empty array for an empty string", () => {
expect(parseAccountIds("")).toEqual([]);
});
it("parses a comma-separated list of ids", () => {
expect(parseAccountIds("3,7,12")).toEqual([3, 7, 12]);
});
it("trims whitespace around tokens", () => {
expect(parseAccountIds(" 3 , 7 ,12")).toEqual([3, 7, 12]);
});
it("parses a single id", () => {
expect(parseAccountIds("42")).toEqual([42]);
});
it("drops non-numeric tokens but keeps the valid ones (rejects non-numeric)", () => {
expect(parseAccountIds("3,abc,7")).toEqual([3, 7]);
});
it("drops decimal tokens (not a finite integer)", () => {
expect(parseAccountIds("3,4.5,7")).toEqual([3, 7]);
});
it("drops empty tokens produced by stray/trailing commas", () => {
expect(parseAccountIds("3,,7,")).toEqual([3, 7]);
});
it("drops exponential/hex-looking tokens that Number() would otherwise coerce", () => {
expect(parseAccountIds("3,1e3,0x10,7")).toEqual([3, 7]);
});
it("drops unsafe-integer tokens (too large to be a reliable id)", () => {
expect(parseAccountIds("3,99999999999999999999,7")).toEqual([3, 7]);
});
it("returns an empty array when every token is invalid", () => {
expect(parseAccountIds("abc,def")).toEqual([]);
});
});
describe("serializeAccountIds", () => {
it("joins ids into a comma-separated string", () => {
expect(serializeAccountIds([3, 7, 12])).toBe("3,7,12");
});
it("serializes a single id", () => {
expect(serializeAccountIds([42])).toBe("42");
});
it("returns null for an empty array so the param is removed (no filter)", () => {
expect(serializeAccountIds([])).toBeNull();
});
it("drops invalid entries (e.g. NaN) before serializing", () => {
expect(serializeAccountIds([3, NaN, 7])).toBe("3,7");
});
it("returns null when every entry is invalid", () => {
expect(serializeAccountIds([NaN, Infinity])).toBeNull();
});
it("round-trips through parseAccountIds", () => {
const ids = [1, 2, 3];
expect(parseAccountIds(serializeAccountIds(ids))).toEqual(ids);
});
});