// The sleep and walk trends draw the selected day against yesterday and the // window average. A day marked "not counted" has to be absent from all three, // and the one that kept slipping through was the selected day itself — it is // the boldest line on the panel, so it reads as the answer. import { load } from "./extract.mjs"; import { suite, eq, ok, report } from "./assert.mjs"; const DAY = 86_400_000; const SEL = new Date(2026, 8, 20); // the day under the cursor const at = (day, hour) => new Date(2026, 8, day, hour).getTime(); let excluded = new Set(); let windowDays = 7; const curves = load({ names: ["startOfDay", "pairWindows", "sleepWindows", "sleepTrendCurves"], stubs: { selectedDay: () => SEL, ymd: (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`, isExcluded: (d) => excluded.has(d.getDate()), chartDays: () => windowDays, }, }); // A night's sleep on each of several days, so every curve has something to draw. const slept = (day, fromHour, toHour) => ([ { id: `s${day}a`, type: "sleep-start", at: at(day, fromHour) }, { id: `s${day}b`, type: "sleep-end", at: at(day, toHour) }, ]); const week = [16, 17, 18, 19, 20].flatMap(d => slept(d, 1, 5)); suite("the selected day's own curve"); { excluded = new Set(); const c = curves.sleepTrendCurves(week); ok(c.today && c.today.length > 1, "a normal day is drawn"); eq(c.dayExcluded, false, "…and not flagged as excluded"); excluded = new Set([20]); // the selected day const m = curves.sleepTrendCurves(week); eq(m.today, null, "a day marked 'not counted' is not drawn at all"); eq(m.dayExcluded, true, "…and says so, so the legend can drop its chip"); eq(m.projected, null, "…and nothing is projected from a curve that isn't there"); ok(m.avg && m.avg.length > 1, "the average it would have been read against survives"); ok(m.yesterday && m.yesterday.length > 1, "so does yesterday"); } suite("the comparison day and the average"); { excluded = new Set([19]); // yesterday, relative to the 20th const c = curves.sleepTrendCurves(week); eq(c.yesterday, null, "a marked yesterday is dropped rather than drawn flat"); ok(c.today && c.today.length > 1, "the selected day is unaffected by it"); // Every day but the selected one marked: nothing left to average over. excluded = new Set([16, 17, 18, 19]); const none = curves.sleepTrendCurves(week); eq(none.avg, null, "an average with no days left to average is null, not zero"); ok(none.today && none.today.length > 1, "…and the selected day still draws"); } suite("a marked day never contributes to the average"); { // The 19th sleeps far longer than the rest. With it counted the average is // dragged up; marked, it should leave no trace. const lopsided = [...[16, 17, 18].flatMap(d => slept(d, 1, 3)), ...slept(19, 1, 23), ...slept(20, 1, 3)]; windowDays = 7; excluded = new Set(); const withIt = curves.sleepTrendCurves(lopsided).avg[24].y; excluded = new Set([19]); const without = curves.sleepTrendCurves(lopsided).avg[24].y; ok(withIt > without, "marking the outlier lowers the average it was inflating"); eq(Math.round(without), 2, "…back to the two hours the remaining days actually slept"); } export default report("trend-curves");