返回 DeepSeek-Reasonix
heartbeat-next-run.test.ts
根目录 / desktop / frontend / src / __tests__ / heartbeat-next-run.test.ts
1 // Run: tsx src/__tests__/heartbeat-next-run.test.ts
2
3 import { heartbeatBuildCycleInterval, heartbeatNextRunAt } from "../custom/features/heartbeat/HeartbeatPanel";
4
5 let passed = 0;
6 let failed = 0;
7
8 function eq(a: unknown, b: unknown, label: string) {
9 if (a === b) {
10 process.stdout.write(` PASS ${label}\n`);
11 passed += 1;
12 } else {
13 process.stdout.write(` FAIL ${label}: expected ${JSON.stringify(b)}, got ${JSON.stringify(a)}\n`);
14 failed += 1;
15 }
16 }
17
18 function localMs(year: number, month: number, day: number, hour: number, minute: number): number {
19 return new Date(year, month - 1, day, hour, minute, 0, 0).getTime();
20 }
21
22 console.log("\nheartbeat next run");
23
24 eq(
25 heartbeatNextRunAt(
26 { interval: "30m", lastRunAt: localMs(2026, 6, 18, 16, 30) },
27 localMs(2026, 6, 18, 17, 20),
28 ),
29 localMs(2026, 6, 18, 17, 0),
30 "plain interval stays due after elapsed",
31 );
32
33 eq(
34 heartbeatNextRunAt(
35 { interval: "30m", lastRunAt: localMs(2026, 6, 18, 16, 30), timeWindowStart: "09:00", timeWindowEnd: "17:00" },
36 localMs(2026, 6, 18, 17, 20),
37 ),
38 localMs(2026, 6, 19, 9, 0),
39 "time window defers elapsed interval to next opening",
40 );
41
42 eq(
43 heartbeatNextRunAt(
44 { interval: "30m", lastRunAt: localMs(2026, 6, 18, 16, 0), timeWindowStart: "09:00", timeWindowEnd: "17:00" },
45 localMs(2026, 6, 18, 16, 10),
46 ),
47 localMs(2026, 6, 18, 16, 30),
48 "time window keeps next run inside the open window",
49 );
50
51 eq(
52 heartbeatNextRunAt(
53 { interval: "30m", lastRunAt: localMs(2026, 6, 18, 21, 50), timeWindowStart: "22:00", timeWindowEnd: "06:00" },
54 localMs(2026, 6, 18, 22, 10),
55 ),
56 localMs(2026, 6, 18, 22, 20),
57 "cross-midnight window keeps due time in the open window",
58 );
59
60 eq(
61 heartbeatNextRunAt(
62 { interval: "30m", lastRunAt: localMs(2026, 6, 18, 11, 30), timeWindowStart: "22:00", timeWindowEnd: "06:00" },
63 localMs(2026, 6, 18, 12, 10),
64 ),
65 localMs(2026, 6, 18, 22, 0),
66 "cross-midnight window waits for today's opening from midday",
67 );
68
69 eq(
70 heartbeatNextRunAt(
71 { interval: "24h|daily@20:00", lastRunAt: localMs(2026, 6, 18, 20, 0), timeWindowStart: "09:00", timeWindowEnd: "17:00" },
72 localMs(2026, 6, 19, 19, 0),
73 ),
74 localMs(2026, 6, 19, 20, 0),
75 "cycle next run ignores stale interval time windows",
76 );
77
78 eq(
79 heartbeatBuildCycleInterval("daily", [], "09:00"),
80 "24h|weekly:mon@09:00",
81 "empty daily day selection does not save as every day",
82 );
83
84 eq(
85 heartbeatBuildCycleInterval("weekly", [], "09:00"),
86 "168h|weekly:mon@09:00",
87 "weekly default uses one weekday",
88 );
89
90 console.log(`\n${passed} passed, ${failed} failed, ${passed + failed} total`);
91 if (failed > 0) process.exit(1);
92
92 lines TYPESCRIPT