evOo-local/packages/core/src/coach.test.ts
Claude e1ce6f4090
test(coach): repository, streak and rhythm suites
Covers what the coach interfaces promise before anything builds on them:
goal and habit ownership and limits, one check-in per habit, goal and day
dated in the user's time zone, streaks across weekends and weeks, and a
rhythm that keeps exactly one main-bot routine per check-in and is
idempotent when set again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 13:15:06 +00:00

67 lines
2.8 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
addDays,
habitDoneInPeriod,
habitStreak,
localDateIn,
weekdayOf,
weekStartOf,
} from "./coach.js";
const dates = (...values: string[]) => new Set(values);
describe("calendar dates", () => {
it("reads the date in the user's time zone, not the server's", () => {
const instant = new Date("2026-10-03T23:30:00Z");
expect(localDateIn("UTC", instant)).toBe("2026-10-03");
expect(localDateIn("Europe/Vienna", instant)).toBe("2026-10-04");
expect(localDateIn("America/Los_Angeles", instant)).toBe("2026-10-03");
expect(localDateIn("Pacific/Kiritimati", new Date("2026-10-03T10:30:00Z"))).toBe("2026-10-04");
});
it("moves across months, years and daylight saving changes by whole days", () => {
expect(addDays("2026-10-31", 1)).toBe("2026-11-01");
expect(addDays("2027-01-01", -1)).toBe("2026-12-31");
expect(addDays("2026-03-28", 2)).toBe("2026-03-30");
expect(weekdayOf("2026-10-03")).toBe(6);
expect(weekStartOf("2026-10-03")).toBe("2026-09-28");
expect(weekStartOf("2026-10-04")).toBe("2026-09-28");
expect(weekStartOf("2026-10-05")).toBe("2026-10-05");
});
});
describe("habit streaks", () => {
const saturday = "2026-10-03";
it("counts consecutive days and keeps today open until it is done", () => {
const done = dates("2026-09-30", "2026-10-01", "2026-10-02");
expect(habitStreak("daily", done, saturday)).toBe(3);
expect(habitStreak("daily", new Set([...done, saturday]), saturday)).toBe(4);
expect(habitStreak("daily", dates("2026-09-30", "2026-10-01"), saturday)).toBe(0);
expect(habitStreak("daily", dates(), saturday)).toBe(0);
});
it("skips weekends for weekday habits", () => {
const monday = "2026-10-05";
const done = dates("2026-10-01", "2026-10-02");
expect(habitStreak("weekdays", done, saturday)).toBe(2);
expect(habitStreak("weekdays", done, monday)).toBe(2);
expect(habitStreak("weekdays", new Set([...done, monday]), monday)).toBe(3);
expect(habitStreak("weekdays", dates("2026-10-01"), monday)).toBe(0);
});
it("counts weeks with at least one done day for weekly habits", () => {
const done = dates("2026-09-15", "2026-09-26", "2026-09-29");
expect(habitStreak("weekly", done, saturday)).toBe(3);
expect(habitStreak("weekly", dates("2026-09-15", "2026-09-26"), saturday)).toBe(2);
expect(habitStreak("weekly", dates("2026-09-15"), saturday)).toBe(0);
});
it("reads a weekly habit as done for the rest of the week", () => {
expect(habitDoneInPeriod("weekly", dates("2026-09-29"), saturday)).toBe(true);
expect(habitDoneInPeriod("weekly", dates("2026-09-27"), saturday)).toBe(false);
expect(habitDoneInPeriod("daily", dates("2026-10-02"), saturday)).toBe(false);
expect(habitDoneInPeriod("daily", dates(saturday), saturday)).toBe(true);
});
});