1import { describe, expect, it } from "vite-plus/test";
2import {
3 formatNextScheduledTaskRun,
4 formatScheduledTaskInterval,
5} from "./scheduledTaskPresentation";
6
7const MINUTE = 60_000;
8const now = new Date(2026, 8, 17, 9).getTime();
9const timeLabel = (date: Date) =>
10 date.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
11
12describe("formatScheduledTaskInterval", () => {
13 it.each([
14 [1, "Every minute"],
15 [15, "Every 15 minutes"],
16 [60, "Every hour"],
17 [90, "Every 1 hour 30 minutes"],
18 [120, "Every 2 hours"],
19 [1440, "Every day"],
20 [1500, "Every 1 day 1 hour"],
21 [2880, "Every 2 days"],
22 [10080, "Every week"],
23 ])("formats a %i-minute interval as %s", (minutes, expected) => {
24 expect(formatScheduledTaskInterval(minutes * MINUTE)).toBe(expected);
25 });
26
27 it("retains precision for legacy sub-minute schedules", () => {
28 expect(formatScheduledTaskInterval(30_000)).toBe("Every 30 seconds");
29 expect(formatScheduledTaskInterval(90_000)).toBe("Every 1 minute 30 seconds");
30 });
31});
32
33describe("formatNextScheduledTaskRun", () => {
34 it.each([
35 [30_000, "Next run in less than a minute"],
36 [MINUTE, "Next run in 1 minute"],
37 [15 * MINUTE, "Next run in 15 minutes"],
38 [60 * MINUTE, "Next run in 1 hour"],
39 [120 * MINUTE, "Next run in 2 hours"],
40 [0, "Next run due"],
41 [-MINUTE, "Next run due"],
42 ])("formats a nearby run %i ms away", (offset, expected) => {
43 expect(formatNextScheduledTaskRun(new Date(now + offset).toISOString(), now)).toBe(expected);
44 });
45
46 it("uses tomorrow by calendar date, including runs just across midnight", () => {
47 const current = new Date(2026, 8, 17, 23, 50);
48 const next = new Date(2026, 8, 18, 0, 10);
49 expect(formatNextScheduledTaskRun(next.toISOString(), current.getTime())).toBe(
50 `Next run tomorrow at ${timeLabel(next)}`,
51 );
52 });
53
54 it("uses the next weekday within a week", () => {
55 const next = new Date(2026, 8, 21, 14, 30);
56 expect(formatNextScheduledTaskRun(next.toISOString(), now)).toBe(
57 `Next run next ${next.toLocaleDateString([], { weekday: "long" })} at ${timeLabel(next)}`,
58 );
59 });
60
61 it("includes the date for more distant runs and the year only when necessary", () => {
62 for (const next of [new Date(2026, 9, 5, 9), new Date(2027, 0, 1, 9)]) {
63 const date = next.toLocaleDateString([], {
64 month: "short",
65 day: "numeric",
66 ...(next.getFullYear() === 2026 ? {} : { year: "numeric" as const }),
67 });
68 expect(formatNextScheduledTaskRun(next.toISOString(), now)).toBe(
69 `Next run ${date} at ${timeLabel(next)}`,
70 );
71 }
72 });
73
74 it("does not show an invalid date", () => {
75 expect(formatNextScheduledTaskRun("invalid", now)).toBe("Next run unavailable");
76 });
77});