functional

This commit is contained in:
2026-09-17 01:19:19 -04:00
commit 3d4d75fdfb
19 changed files with 3257 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
import { describe, it, expect } from "vitest";
import { estimate, totalActiveDays, activeDaysConsumed } from "./engine";
import type { Config, Selections } from "./types";
import { parseISODate } from "./dates";
function d(iso: string): Date {
const parsed = parseISODate(iso);
if (!parsed) throw new Error(`bad test date ${iso}`);
return parsed;
}
// A simple 100-day window (Jan 1 .. Apr 10, 2026 is 100 inclusive days).
const config: Config = {
mealStartDate: d("2026-01-01"),
mealEndDate: d("2026-04-10"),
plans: [
{ id: "green", displayName: "Green", totalMealBlocks: 200, totalFlexDollars: 500 },
],
missingPeriods: [
{ id: "break", displayName: "Break", startDate: d("2026-02-01"), endDate: d("2026-02-10") }, // 10 days
],
};
function sel(overrides: Partial<Selections> = {}): Selections {
return {
selectedPlanId: "green",
targetDate: d("2026-04-10"),
activeMissingPeriodIds: new Set(),
extraExcludedDays: 0,
...overrides,
};
}
describe("totalActiveDays", () => {
it("counts the full inclusive window with no missing periods", () => {
expect(totalActiveDays(config, new Set())).toBe(100);
});
it("subtracts an active missing period", () => {
expect(totalActiveDays(config, new Set(["break"]))).toBe(90);
});
});
describe("activeDaysConsumed", () => {
it("is the full window at the end date", () => {
expect(activeDaysConsumed(config, sel())).toBe(100);
});
it("clamps a target date before the start to zero", () => {
expect(activeDaysConsumed(config, sel({ targetDate: d("2025-12-01") }))).toBe(0);
});
it("subtracts extra excluded days", () => {
expect(activeDaysConsumed(config, sel({ extraExcludedDays: 5 }))).toBe(95);
});
});
describe("estimate", () => {
it("conserves the budget at the end date with no exclusions", () => {
const r = estimate(config, sel());
expect(r.consumedMealBlocks).toBeCloseTo(200);
expect(r.remainingMealBlocks).toBeCloseTo(0);
expect(r.consumedFlexDollars).toBeCloseTo(500);
expect(r.remainingFlexDollars).toBeCloseTo(0);
});
it("consumed + remaining always equals the plan total", () => {
const r = estimate(config, sel({ targetDate: d("2026-02-15"), activeMissingPeriodIds: new Set(["break"]) }));
expect(r.consumedMealBlocks + r.remainingMealBlocks).toBeCloseTo(200);
expect(r.consumedFlexDollars + r.remainingFlexDollars).toBeCloseTo(500);
});
it("computes a per-week rate of dailyRate * 7", () => {
const r = estimate(config, sel());
// 200 blocks / 100 days = 2/day => 14/week
expect(r.perWeekMealBlocks).toBeCloseTo(14);
expect(r.perWeekFlexDollars).toBeCloseTo(35); // 500/100*7
});
it("never lets excluding days increase consumption (monotonicity)", () => {
const base = estimate(config, sel({ targetDate: d("2026-03-01") }));
const more = estimate(config, sel({ targetDate: d("2026-03-01"), extraExcludedDays: 10 }));
expect(more.consumedMealBlocks).toBeLessThanOrEqual(base.consumedMealBlocks);
});
it("handles zero active days without dividing by zero", () => {
const allExcluded: Config = {
...config,
missingPeriods: [
{ id: "all", displayName: "All", startDate: d("2026-01-01"), endDate: d("2026-04-10") },
],
};
const r = estimate(allExcluded, sel({ activeMissingPeriodIds: new Set(["all"]) }));
expect(r.perWeekMealBlocks).toBe(0);
expect(r.consumedMealBlocks).toBe(0);
expect(Number.isFinite(r.remainingMealBlocks)).toBe(true);
});
});