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
67 lines
2.8 KiB
TypeScript
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);
|
|
});
|
|
});
|