// @effect-diagnostics globalDate:off -- Tests exercise local calendar snooze boundaries.import { ThreadId } from "@t3tools/contracts";import { TurnId } from "@t3tools/contracts";import { describe, expect, it } from "vite-plus/test"; import { canSnooze, effectiveSnoozed, hasQueuedTurnStart, resolveSnoozePresets, snoozeWakeLabel, threadRaisedHandWhileSnoozed, threadWokeAt, type ThreadSnoozeShell,} from "./threadSettled.ts";import type { OrchestrationThreadShell } from "@t3tools/contracts"; const NOW = "2026-04-10T12:00:00.000Z";const SNOOZED_AT = "2026-04-10T09:00:00.000Z";const FUTURE_WAKE = "2026-04-11T09:00:00.000Z";const PAST_WAKE = "2026-04-10T10:00:00.000Z"; function localDate(year: number, month: number, day: number, hour: number, minute = 0): Date { return new Date(year, month - 1, day, hour, minute, 0, 0);} function makeShell(input: { readonly snoozedUntil?: string | null; readonly snoozedAt?: string | null; readonly sessionStatus?: "starting" | "running" | "ready" | "error"; readonly pending?: "approval" | "user-input"; readonly turnCompletedAt?: string | null;}): ThreadSnoozeShell { const threadId = ThreadId.make("thread-1"); return { snoozedUntil: input.snoozedUntil ?? null, snoozedAt: input.snoozedAt ?? (input.snoozedUntil != null ? SNOOZED_AT : null), hasPendingApprovals: input.pending === "approval", hasPendingUserInput: input.pending === "user-input", session: input.sessionStatus === undefined ? null : { threadId, status: input.sessionStatus, providerName: "Codex", runtimeMode: "full-access", activeTurnId: null, lastError: input.sessionStatus === "error" ? "boom" : null, updatedAt: "2026-04-10T11:00:00.000Z", }, latestTurn: input.turnCompletedAt === undefined ? null : { turnId: TurnId.make("turn-1"), state: "completed", requestedAt: SNOOZED_AT, startedAt: null, completedAt: input.turnCompletedAt, assistantMessageId: null, }, };} type QueuedTurnShell = Pick< OrchestrationThreadShell, "latestUserMessageAt" | "latestTurn" | "session">; function makeQueuedTurnShell(overrides: Partial<QueuedTurnShell> = {}): QueuedTurnShell { return { latestUserMessageAt: null, latestTurn: null, session: null, ...overrides };} describe("effectiveSnoozed", () => { it("hides a thread whose wake time is in the future", () => { expect(effectiveSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE }), { now: NOW })).toBe(true); }); it("stops classifying as snoozed once the wake time passes (timer wake, no event)", () => { expect(effectiveSnoozed(makeShell({ snoozedUntil: PAST_WAKE }), { now: NOW })).toBe(false); }); it("never snoozes a thread with no snooze state", () => { expect(effectiveSnoozed(makeShell({}), { now: NOW })).toBe(false); }); it("never hides on malformed wake data", () => { expect(effectiveSnoozed(makeShell({ snoozedUntil: "not-a-date" }), { now: NOW })).toBe(false); }); it("wakes early when the agent is blocked on the user", () => { expect( effectiveSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE, pending: "approval" }), { now: NOW, }), ).toBe(false); expect( effectiveSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE, pending: "user-input" }), { now: NOW, }), ).toBe(false); }); it("wakes early on a failure that happened after the snooze", () => { // makeShell stamps session.updatedAt at 11:00, after SNOOZED_AT (9:00). expect( effectiveSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE, sessionStatus: "error" }), { now: NOW, }), ).toBe(false); }); it("stays snoozed when the failure predates the snooze — the user saw it", () => { expect( effectiveSnoozed( makeShell({ snoozedUntil: FUTURE_WAKE, sessionStatus: "error", // Snoozed AFTER the error's status edge. snoozedAt: "2026-04-10T11:30:00.000Z", }), { now: NOW }, ), ).toBe(true); }); it("stays snoozed while the session keeps working — snooze never pauses the agent", () => { expect( effectiveSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE, sessionStatus: "running" }), { now: NOW, }), ).toBe(true); }); it("wakes early when a run completes after the snooze was set", () => { expect( effectiveSnoozed( makeShell({ snoozedUntil: FUTURE_WAKE, turnCompletedAt: "2026-04-10T10:30:00.000Z" }), { now: NOW }, ), ).toBe(false); }); it("ignores runs that completed before the snooze — the user saw that result", () => { expect( effectiveSnoozed( makeShell({ snoozedUntil: FUTURE_WAKE, turnCompletedAt: "2026-04-10T08:00:00.000Z" }), { now: NOW }, ), ).toBe(true); });}); describe("threadRaisedHandWhileSnoozed", () => { it("is false for a quiet snoozed thread", () => { expect(threadRaisedHandWhileSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE }))).toBe(false); }); it("is true for approvals, input, and failures", () => { expect( threadRaisedHandWhileSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE, pending: "approval" })), ).toBe(true); expect( threadRaisedHandWhileSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE, pending: "user-input" })), ).toBe(true); expect( threadRaisedHandWhileSnoozed( makeShell({ snoozedUntil: FUTURE_WAKE, sessionStatus: "error" }), ), ).toBe(true); });}); describe("canSnooze", () => { it("allows snoozing quiet and working threads alike", () => { expect(canSnooze({ ...makeShell({}), latestUserMessageAt: null }, { now: NOW })).toBe(true); expect( canSnooze( { ...makeShell({ sessionStatus: "running" }), latestUserMessageAt: null }, { now: NOW }, ), ).toBe(true); }); it("refuses blocked-on-you work", () => { expect( canSnooze({ ...makeShell({ pending: "approval" }), latestUserMessageAt: null }, { now: NOW }), ).toBe(false); expect( canSnooze( { ...makeShell({ pending: "user-input" }), latestUserMessageAt: null }, { now: NOW }, ), ).toBe(false); }); it("refuses a queued turn start — same invisible-pending-work rule as settle", () => { // Fresh user message, no turn has adopted it, within the grace window. expect( canSnooze( { ...makeShell({}), latestUserMessageAt: "2026-04-10T11:59:30.000Z" }, { now: NOW }, ), ).toBe(false); // Outside the grace window the message is stale data, not queued work. expect( canSnooze( { ...makeShell({}), latestUserMessageAt: "2026-04-10T11:00:00.000Z" }, { now: NOW }, ), ).toBe(true); });}); describe("hasQueuedTurnStart", () => { it("expires queued state after two minutes", () => { const thread = makeQueuedTurnShell({ latestUserMessageAt: "2026-04-10T11:57:59.000Z", }); expect(hasQueuedTurnStart(thread, { now: NOW })).toBe(false); }); it("clears queued state when a turn adopts the message or the session fails", () => { const messageAt = "2026-04-10T11:59:00.000Z"; const adopted = makeQueuedTurnShell({ latestUserMessageAt: messageAt, latestTurn: { turnId: TurnId.make("turn-adopted"), state: "running", requestedAt: messageAt, startedAt: null, completedAt: null, assistantMessageId: null, }, }); const failed = makeQueuedTurnShell({ latestUserMessageAt: messageAt, session: { threadId: ThreadId.make("thread-failed"), status: "error", providerName: "Codex", runtimeMode: "full-access", activeTurnId: null, lastError: "failed", updatedAt: NOW, }, }); expect(hasQueuedTurnStart(adopted, { now: NOW })).toBe(false); expect(hasQueuedTurnStart(failed, { now: NOW })).toBe(false); }); it("bounds future client clock skew", () => { const farAhead = makeQueuedTurnShell({ latestUserMessageAt: "2026-04-10T12:03:00.000Z", }); const slightlyAhead = makeQueuedTurnShell({ latestUserMessageAt: "2026-04-10T12:01:00.000Z", }); expect(hasQueuedTurnStart(farAhead, { now: NOW })).toBe(false); expect(hasQueuedTurnStart(slightlyAhead, { now: NOW })).toBe(true); });}); describe("threadWokeAt", () => { it("is null for never-snoozed and still-snoozed threads", () => { expect(threadWokeAt(makeShell({}), { now: NOW })).toBe(null); expect(threadWokeAt(makeShell({ snoozedUntil: FUTURE_WAKE }), { now: NOW })).toBe(null); }); it("reports the wake time for a timer wake", () => { expect(threadWokeAt(makeShell({ snoozedUntil: PAST_WAKE }), { now: NOW })).toBe(PAST_WAKE); }); it("reports the completion time for an early run-completed wake", () => { expect( threadWokeAt( makeShell({ snoozedUntil: FUTURE_WAKE, turnCompletedAt: "2026-04-10T10:30:00.000Z" }), { now: NOW }, ), ).toBe("2026-04-10T10:30:00.000Z"); }); it("falls back to session activity for blocked/failed early wakes", () => { expect( threadWokeAt(makeShell({ snoozedUntil: FUTURE_WAKE, sessionStatus: "error" }), { now: NOW, }), ).toBe("2026-04-10T11:00:00.000Z"); }); it("keeps the early wake authoritative after the scheduled time passes", () => { // Woke early at 10:30 via run-completed; the scheduled wake (PAST_WAKE // 10:00 relative to a later now) has ALSO passed. Reporting the // scheduled time would resurface a Woke pill the user already cleared // by visiting between the early wake and now. expect( threadWokeAt( makeShell({ snoozedUntil: PAST_WAKE, turnCompletedAt: "2026-04-10T09:30:00.000Z" }), { now: NOW }, ), ).toBe("2026-04-10T09:30:00.000Z"); });}); describe("snoozeWakeLabel", () => { const now = "2026-06-02T00:00:00.000Z"; it("formats remaining time coarsely, rounding up", () => { expect(snoozeWakeLabel("2026-06-02T00:30:00.000Z", { now })).toBe("30m"); expect(snoozeWakeLabel("2026-06-02T01:30:00.000Z", { now })).toBe("2h"); expect(snoozeWakeLabel("2026-06-03T02:00:00.000Z", { now })).toBe("2d"); }); it("never reads zero or negative while still snoozed", () => { expect(snoozeWakeLabel("2026-06-02T00:00:30.000Z", { now })).toBe("1m"); expect(snoozeWakeLabel("2026-06-01T23:59:59.000Z", { now })).toBe("now"); expect(snoozeWakeLabel("not-a-date", { now })).toBe("now"); expect(snoozeWakeLabel("2026-06-02T09:00:00.000Z", { now: "bad" })).toBe("now"); });}); describe("resolveSnoozePresets", () => { it("offers the shared desktop and mobile choices", () => { const presets = resolveSnoozePresets(localDate(2026, 4, 8, 10)); expect(presets.map((preset) => preset.id)).toEqual([ "hour", "three-hours", "evening", "tomorrow", "next-week", ]); expect(presets.find((preset) => preset.id === "three-hours")?.snoozedUntil).toBe( localDate(2026, 4, 8, 13).toISOString(), ); expect(presets.find((preset) => preset.id === "three-hours")?.label).toBe("In 3 hours"); expect(presets.find((preset) => preset.id === "evening")?.label).toBe("This evening"); expect( new Date(presets.find((preset) => preset.id === "tomorrow")!.snoozedUntil).getHours(), ).toBe(9); }); it("drops the evening choice once evening is near or past", () => { expect(resolveSnoozePresets(localDate(2026, 4, 8, 17, 30)).map((preset) => preset.id)).toEqual([ "hour", "three-hours", "tomorrow", "next-week", ]); }); it("puts next week on the following Monday", () => { const nextWeek = new Date( resolveSnoozePresets(localDate(2026, 4, 6, 10)).find((preset) => preset.id === "next-week")! .snoozedUntil, ); expect(nextWeek.getDay()).toBe(1); expect(nextWeek.getDate()).toBe(13); }); it("drops next week on Sundays, when it lands on the same Monday as tomorrow", () => { // Sunday 2026-08-30 07:01: "Tomorrow" and "Next week" are both Monday 9:00. const presets = resolveSnoozePresets(localDate(2026, 8, 30, 7, 1)); expect(presets.map((preset) => preset.id)).toEqual([ "hour", "three-hours", "evening", "tomorrow", ]); const tomorrow = new Date(presets.find((preset) => preset.id === "tomorrow")!.snoozedUntil); expect(tomorrow.getDay()).toBe(1); });});
// @effect-diagnostics globalDate:off -- Tests exercise local calendar snooze boundaries.
import { ThreadId } from "@t3tools/contracts";
import { TurnId } from "@t3tools/contracts";
import { describe, expect, it } from "vite-plus/test";
import {
canSnooze,
effectiveSnoozed,
hasQueuedTurnStart,
resolveSnoozePresets,
snoozeWakeLabel,
threadRaisedHandWhileSnoozed,
threadWokeAt,
type ThreadSnoozeShell,
} from "./threadSettled.ts";
import type { OrchestrationThreadShell } from "@t3tools/contracts";
const NOW = "2026-04-10T12:00:00.000Z";
const SNOOZED_AT = "2026-04-10T09:00:00.000Z";
const FUTURE_WAKE = "2026-04-11T09:00:00.000Z";
const PAST_WAKE = "2026-04-10T10:00:00.000Z";
function localDate(year: number, month: number, day: number, hour: number, minute = 0): Date {
return new Date(year, month - 1, day, hour, minute, 0, 0);
}
function makeShell(input: {
readonly snoozedUntil?: string | null;
readonly snoozedAt?: string | null;
readonly sessionStatus?: "starting" | "running" | "ready" | "error";
readonly pending?: "approval" | "user-input";
readonly turnCompletedAt?: string | null;
}): ThreadSnoozeShell {
const threadId = ThreadId.make("thread-1");
return {
snoozedUntil: input.snoozedUntil ?? null,
snoozedAt: input.snoozedAt ?? (input.snoozedUntil != null ? SNOOZED_AT : null),
hasPendingApprovals: input.pending === "approval",
hasPendingUserInput: input.pending === "user-input",
session:
input.sessionStatus === undefined
? null
: {
threadId,
status: input.sessionStatus,
providerName: "Codex",
runtimeMode: "full-access",
activeTurnId: null,
lastError: input.sessionStatus === "error" ? "boom" : null,
updatedAt: "2026-04-10T11:00:00.000Z",
},
latestTurn:
input.turnCompletedAt === undefined
? null
: {
turnId: TurnId.make("turn-1"),
state: "completed",
requestedAt: SNOOZED_AT,
startedAt: null,
completedAt: input.turnCompletedAt,
assistantMessageId: null,
},
};
}
type QueuedTurnShell = Pick<
OrchestrationThreadShell,
"latestUserMessageAt" | "latestTurn" | "session"
>;
function makeQueuedTurnShell(overrides: Partial<QueuedTurnShell> = {}): QueuedTurnShell {
return { latestUserMessageAt: null, latestTurn: null, session: null, ...overrides };
}
describe("effectiveSnoozed", () => {
it("hides a thread whose wake time is in the future", () => {
expect(effectiveSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE }), { now: NOW })).toBe(true);
});
it("stops classifying as snoozed once the wake time passes (timer wake, no event)", () => {
expect(effectiveSnoozed(makeShell({ snoozedUntil: PAST_WAKE }), { now: NOW })).toBe(false);
});
it("never snoozes a thread with no snooze state", () => {
expect(effectiveSnoozed(makeShell({}), { now: NOW })).toBe(false);
});
it("never hides on malformed wake data", () => {
expect(effectiveSnoozed(makeShell({ snoozedUntil: "not-a-date" }), { now: NOW })).toBe(false);
});
it("wakes early when the agent is blocked on the user", () => {
expect(
effectiveSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE, pending: "approval" }), {
now: NOW,
}),
).toBe(false);
expect(
effectiveSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE, pending: "user-input" }), {
now: NOW,
}),
).toBe(false);
});
it("wakes early on a failure that happened after the snooze", () => {
// makeShell stamps session.updatedAt at 11:00, after SNOOZED_AT (9:00).
expect(
effectiveSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE, sessionStatus: "error" }), {
now: NOW,
}),
).toBe(false);
});
it("stays snoozed when the failure predates the snooze — the user saw it", () => {
expect(
effectiveSnoozed(
makeShell({
snoozedUntil: FUTURE_WAKE,
sessionStatus: "error",
// Snoozed AFTER the error's status edge.
snoozedAt: "2026-04-10T11:30:00.000Z",
}),
{ now: NOW },
),
).toBe(true);
});
it("stays snoozed while the session keeps working — snooze never pauses the agent", () => {
expect(
effectiveSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE, sessionStatus: "running" }), {
now: NOW,
}),
).toBe(true);
});
it("wakes early when a run completes after the snooze was set", () => {
expect(
effectiveSnoozed(
makeShell({ snoozedUntil: FUTURE_WAKE, turnCompletedAt: "2026-04-10T10:30:00.000Z" }),
{ now: NOW },
),
).toBe(false);
});
it("ignores runs that completed before the snooze — the user saw that result", () => {
expect(
effectiveSnoozed(
makeShell({ snoozedUntil: FUTURE_WAKE, turnCompletedAt: "2026-04-10T08:00:00.000Z" }),
{ now: NOW },
),
).toBe(true);
});
});
describe("threadRaisedHandWhileSnoozed", () => {
it("is false for a quiet snoozed thread", () => {
expect(threadRaisedHandWhileSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE }))).toBe(false);
});
it("is true for approvals, input, and failures", () => {
expect(
threadRaisedHandWhileSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE, pending: "approval" })),
).toBe(true);
expect(
threadRaisedHandWhileSnoozed(makeShell({ snoozedUntil: FUTURE_WAKE, pending: "user-input" })),
).toBe(true);
expect(
threadRaisedHandWhileSnoozed(
makeShell({ snoozedUntil: FUTURE_WAKE, sessionStatus: "error" }),
),
).toBe(true);
});
});
describe("canSnooze", () => {
it("allows snoozing quiet and working threads alike", () => {
expect(canSnooze({ ...makeShell({}), latestUserMessageAt: null }, { now: NOW })).toBe(true);
expect(
canSnooze(
{ ...makeShell({ sessionStatus: "running" }), latestUserMessageAt: null },
{ now: NOW },
),
).toBe(true);
});
it("refuses blocked-on-you work", () => {
expect(
canSnooze({ ...makeShell({ pending: "approval" }), latestUserMessageAt: null }, { now: NOW }),
).toBe(false);
expect(
canSnooze(
{ ...makeShell({ pending: "user-input" }), latestUserMessageAt: null },
{ now: NOW },
),
).toBe(false);
});
it("refuses a queued turn start — same invisible-pending-work rule as settle", () => {
// Fresh user message, no turn has adopted it, within the grace window.
expect(
canSnooze(
{ ...makeShell({}), latestUserMessageAt: "2026-04-10T11:59:30.000Z" },
{ now: NOW },
),
).toBe(false);
// Outside the grace window the message is stale data, not queued work.
expect(
canSnooze(
{ ...makeShell({}), latestUserMessageAt: "2026-04-10T11:00:00.000Z" },
{ now: NOW },
),
).toBe(true);
});
});
describe("hasQueuedTurnStart", () => {
it("expires queued state after two minutes", () => {
const thread = makeQueuedTurnShell({
latestUserMessageAt: "2026-04-10T11:57:59.000Z",
});
expect(hasQueuedTurnStart(thread, { now: NOW })).toBe(false);
});
it("clears queued state when a turn adopts the message or the session fails", () => {
const messageAt = "2026-04-10T11:59:00.000Z";
const adopted = makeQueuedTurnShell({
latestUserMessageAt: messageAt,
latestTurn: {
turnId: TurnId.make("turn-adopted"),
state: "running",
requestedAt: messageAt,
startedAt: null,
completedAt: null,
assistantMessageId: null,
},
});
const failed = makeQueuedTurnShell({
latestUserMessageAt: messageAt,
session: {
threadId: ThreadId.make("thread-failed"),
status: "error",
providerName: "Codex",
runtimeMode: "full-access",
activeTurnId: null,
lastError: "failed",
updatedAt: NOW,
},
});
expect(hasQueuedTurnStart(adopted, { now: NOW })).toBe(false);
expect(hasQueuedTurnStart(failed, { now: NOW })).toBe(false);
});
it("bounds future client clock skew", () => {
const farAhead = makeQueuedTurnShell({
latestUserMessageAt: "2026-04-10T12:03:00.000Z",
});
const slightlyAhead = makeQueuedTurnShell({
latestUserMessageAt: "2026-04-10T12:01:00.000Z",
});
expect(hasQueuedTurnStart(farAhead, { now: NOW })).toBe(false);
expect(hasQueuedTurnStart(slightlyAhead, { now: NOW })).toBe(true);
});
});
describe("threadWokeAt", () => {
it("is null for never-snoozed and still-snoozed threads", () => {
expect(threadWokeAt(makeShell({}), { now: NOW })).toBe(null);
expect(threadWokeAt(makeShell({ snoozedUntil: FUTURE_WAKE }), { now: NOW })).toBe(null);
});
it("reports the wake time for a timer wake", () => {
expect(threadWokeAt(makeShell({ snoozedUntil: PAST_WAKE }), { now: NOW })).toBe(PAST_WAKE);
});
it("reports the completion time for an early run-completed wake", () => {
expect(
threadWokeAt(
makeShell({ snoozedUntil: FUTURE_WAKE, turnCompletedAt: "2026-04-10T10:30:00.000Z" }),
{ now: NOW },
),
).toBe("2026-04-10T10:30:00.000Z");
});
it("falls back to session activity for blocked/failed early wakes", () => {
expect(
threadWokeAt(makeShell({ snoozedUntil: FUTURE_WAKE, sessionStatus: "error" }), {
now: NOW,
}),
).toBe("2026-04-10T11:00:00.000Z");
});
it("keeps the early wake authoritative after the scheduled time passes", () => {
// Woke early at 10:30 via run-completed; the scheduled wake (PAST_WAKE
// 10:00 relative to a later now) has ALSO passed. Reporting the
// scheduled time would resurface a Woke pill the user already cleared
// by visiting between the early wake and now.
expect(
threadWokeAt(
makeShell({ snoozedUntil: PAST_WAKE, turnCompletedAt: "2026-04-10T09:30:00.000Z" }),
{ now: NOW },
),
).toBe("2026-04-10T09:30:00.000Z");
});
});
describe("snoozeWakeLabel", () => {
const now = "2026-06-02T00:00:00.000Z";
it("formats remaining time coarsely, rounding up", () => {
expect(snoozeWakeLabel("2026-06-02T00:30:00.000Z", { now })).toBe("30m");
expect(snoozeWakeLabel("2026-06-02T01:30:00.000Z", { now })).toBe("2h");
expect(snoozeWakeLabel("2026-06-03T02:00:00.000Z", { now })).toBe("2d");
});
it("never reads zero or negative while still snoozed", () => {
expect(snoozeWakeLabel("2026-06-02T00:00:30.000Z", { now })).toBe("1m");
expect(snoozeWakeLabel("2026-06-01T23:59:59.000Z", { now })).toBe("now");
expect(snoozeWakeLabel("not-a-date", { now })).toBe("now");
expect(snoozeWakeLabel("2026-06-02T09:00:00.000Z", { now: "bad" })).toBe("now");
});
});
describe("resolveSnoozePresets", () => {
it("offers the shared desktop and mobile choices", () => {
const presets = resolveSnoozePresets(localDate(2026, 4, 8, 10));
expect(presets.map((preset) => preset.id)).toEqual([
"hour",
"three-hours",
"evening",
"tomorrow",
"next-week",
]);
expect(presets.find((preset) => preset.id === "three-hours")?.snoozedUntil).toBe(
localDate(2026, 4, 8, 13).toISOString(),
);
expect(presets.find((preset) => preset.id === "three-hours")?.label).toBe("In 3 hours");
expect(presets.find((preset) => preset.id === "evening")?.label).toBe("This evening");
expect(
new Date(presets.find((preset) => preset.id === "tomorrow")!.snoozedUntil).getHours(),
).toBe(9);
});
it("drops the evening choice once evening is near or past", () => {
expect(resolveSnoozePresets(localDate(2026, 4, 8, 17, 30)).map((preset) => preset.id)).toEqual([
"hour",
"three-hours",
"tomorrow",
"next-week",
]);
});
it("puts next week on the following Monday", () => {
const nextWeek = new Date(
resolveSnoozePresets(localDate(2026, 4, 6, 10)).find((preset) => preset.id === "next-week")!
.snoozedUntil,
);
expect(nextWeek.getDay()).toBe(1);
expect(nextWeek.getDate()).toBe(13);
});
it("drops next week on Sundays, when it lands on the same Monday as tomorrow", () => {
// Sunday 2026-08-30 07:01: "Tomorrow" and "Next week" are both Monday 9:00.
const presets = resolveSnoozePresets(localDate(2026, 8, 30, 7, 1));
expect(presets.map((preset) => preset.id)).toEqual([
"hour",
"three-hours",
"evening",
"tomorrow",
]);
const tomorrow = new Date(presets.find((preset) => preset.id === "tomorrow")!.snoozedUntil);
expect(tomorrow.getDay()).toBe(1);
});
});