96 lines
2.9 KiB
TypeScript
96 lines
2.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
hourlyTimelineTicks,
|
|
sleepAgendaTicks,
|
|
sleepTimelineInterval,
|
|
sleepTimelinePosition,
|
|
timelineInterval,
|
|
timelinePosition,
|
|
} from "./sleepTimelineGeometry";
|
|
|
|
describe("sleep timeline geometry", () => {
|
|
it.each([
|
|
["2026-09-20T18:00:00Z", 0],
|
|
["2026-09-21T00:00:00Z", 0.25],
|
|
["2026-09-21T06:00:00Z", 0.5],
|
|
["2026-09-21T12:00:00Z", 0.75],
|
|
["2026-09-21T18:00:00Z", 1],
|
|
["2026-09-20T22:30:00Z", 0.1875],
|
|
["2026-09-21T04:45:00Z", 0.4479166667],
|
|
["2026-09-21T15:30:00Z", 0.8958333333],
|
|
])("projects %s onto the shared 18:00 axis", (timestamp, expected) => {
|
|
expect(sleepTimelinePosition(timestamp, "2026-09-20")).toBeCloseTo(
|
|
expected,
|
|
9,
|
|
);
|
|
});
|
|
|
|
it("uses the same axis for interval left and width", () => {
|
|
const interval = sleepTimelineInterval(
|
|
"2026-09-20T22:30:00Z",
|
|
"2026-09-21T03:30:00Z",
|
|
"2026-09-20",
|
|
);
|
|
expect(interval.left).toBeCloseTo(0.1875, 12);
|
|
expect(interval.width).toBeCloseTo(5 / 24, 12);
|
|
});
|
|
|
|
it("keeps one absolute origin when event offsets differ", () => {
|
|
expect(
|
|
sleepTimelinePosition(
|
|
"2026-10-25T05:00:00+01:00",
|
|
"2026-10-24",
|
|
"2026-10-24T18:00:00+02:00",
|
|
),
|
|
).toBeCloseTo(0.5, 12);
|
|
});
|
|
|
|
it("projects arbitrary chart windows with the same bounded formula", () => {
|
|
const start = Date.parse("2026-09-20T22:05:00+02:00");
|
|
const end = Date.parse("2026-09-21T04:14:00+02:00");
|
|
expect(timelinePosition(start, start, end)).toBe(0);
|
|
expect(timelinePosition(end, start, end)).toBe(1);
|
|
const interval = timelineInterval(
|
|
"2026-09-20T22:50:00+02:00",
|
|
"2026-09-21T04:04:00+02:00",
|
|
start,
|
|
end,
|
|
);
|
|
expect(interval.left).toBeCloseTo(45 / 369, 12);
|
|
expect(interval.width).toBeCloseTo(314 / 369, 12);
|
|
});
|
|
|
|
it("keeps real bounds and emits every intervening full hour", () => {
|
|
const start = Date.parse("2026-09-20T22:05:00+02:00");
|
|
const end = Date.parse("2026-09-21T04:14:00+02:00");
|
|
const ticks = hourlyTimelineTicks(start, end);
|
|
expect(ticks.map((tick) => new Date(tick.timestamp).getHours())).toEqual([
|
|
23, 0, 1, 2, 3, 4,
|
|
]);
|
|
expect(ticks.every((tick) => tick.position > 0 && tick.position < 1)).toBe(
|
|
true,
|
|
);
|
|
});
|
|
|
|
it("derives exact one-hour and two-hour 18:00-to-18:00 ticks", () => {
|
|
const hourly = sleepAgendaTicks();
|
|
expect(hourly).toHaveLength(24);
|
|
expect(hourly[0]).toEqual({ hourOffset: 0, position: 1 / 48, label: "18" });
|
|
expect(hourly[6]).toEqual({ hourOffset: 6, position: 13 / 48, label: "00" });
|
|
expect(hourly[23]).toEqual({ hourOffset: 23, position: 47 / 48, label: "17" });
|
|
expect(sleepAgendaTicks(2).map((tick) => tick.label)).toEqual([
|
|
"18",
|
|
"20",
|
|
"22",
|
|
"00",
|
|
"02",
|
|
"04",
|
|
"06",
|
|
"08",
|
|
"10",
|
|
"12",
|
|
"14",
|
|
"16",
|
|
]);
|
|
});
|
|
});
|