__tests__/schedule-due.test.js
Rama main · réplica de lectura del repositorio original.
/*
* WHEN A WATCHER IS DUE — and, for a daily one, why a single minute was not enough.
*
* The daily rule used to be due only ON its minute: hours === hh && minutes === mm. The scheduler
* ticks once a minute, so that is ONE chance a day, and it is lost whenever another pass holds the
* browser at that minute (four watchers on this box run every 10 to 20 minutes), whenever the pod
* rolls through it, or whenever a tick lands a second late. The property then goes unread for the day,
* silently, while the card shows the previous reading as the newest there is.
*
* These tests pin the window that replaced it, and the gap gate that keeps a window from being a loop.
*/
import { describe, it, expect } from 'vitest';
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const wfs = require('../src/workflows');
const at = (s) => new Date(s);
const HOUR = 3600e3;
const daily = { type: 'schedule', every: 'day', at: '07:20' };
describe('a daily watcher', () => {
/* Yesterday evening at 20:01, so the 22-hour gap is open by 07:20 the day after tomorrow. */
const lastNight = at('2026-09-20T20:01:00').getTime();
it('fires on its minute', () => {
expect(wfs.scheduleDue(daily, lastNight, at('2026-09-22T07:20:30'))).toBe(true);
});
/* THE BUG: at 07:21 the old rule said no, and the next yes was twenty-four hours later. */
it('still fires a minute late, because the browser was busy on the minute', () => {
expect(wfs.scheduleDue(daily, lastNight, at('2026-09-22T07:21:00'))).toBe(true);
});
it('and hours late, because reading late beats not reading', () => {
expect(wfs.scheduleDue(daily, lastNight, at('2026-09-22T14:00:00'))).toBe(true);
expect(wfs.scheduleDue(daily, lastNight, at('2026-09-22T23:59:00'))).toBe(true);
});
it('does not fire before its time', () => {
expect(wfs.scheduleDue(daily, lastNight, at('2026-09-22T07:19:00'))).toBe(false);
expect(wfs.scheduleDue(daily, lastNight, at('2026-09-22T00:05:00'))).toBe(false);
});
/* The gap is what makes the window one pass rather than every tick for the rest of the day. */
it('fires once, not on every tick after its time', () => {
const ranToday = at('2026-09-22T07:20:10').getTime();
expect(wfs.scheduleDue(daily, ranToday, at('2026-09-22T07:21:00'))).toBe(false);
expect(wfs.scheduleDue(daily, ranToday, at('2026-09-22T18:00:00'))).toBe(false);
/* And the next day it is due again. */
expect(wfs.scheduleDue(daily, ranToday, at('2026-09-23T07:20:05'))).toBe(true);
});
it('is due as soon as it may be when it has never run', () => {
expect(wfs.scheduleDue(daily, 0, at('2026-09-22T07:20:00'))).toBe(true);
expect(wfs.scheduleDue(daily, 0, at('2026-09-22T06:00:00'))).toBe(false);
});
it('defaults to 08:00 when no time is set', () => {
const noTime = { type: 'schedule', every: 'day' };
expect(wfs.scheduleDue(noTime, 0, at('2026-09-22T07:59:00'))).toBe(false);
expect(wfs.scheduleDue(noTime, 0, at('2026-09-22T08:00:00'))).toBe(true);
});
});
describe('the interval watchers, unchanged', () => {
it('fires once its interval has passed', () => {
const t = at('2026-09-22T08:00:00');
const fifteen = { type: 'schedule', every: 'minute', n: 15 };
expect(wfs.scheduleDue(fifteen, t.getTime() - 14 * 60e3, t)).toBe(false);
expect(wfs.scheduleDue(fifteen, t.getTime() - 15 * 60e3, t)).toBe(true);
const hourly = { type: 'schedule', every: 'hour', n: 2 };
expect(wfs.scheduleDue(hourly, t.getTime() - HOUR, t)).toBe(false);
expect(wfs.scheduleDue(hourly, t.getTime() - 2 * HOUR, t)).toBe(true);
});
it('treats a missing n as one, and an unknown unit as never', () => {
const t = at('2026-09-22T08:00:00');
expect(wfs.scheduleDue({ type: 'schedule', every: 'minute' }, t.getTime() - 60e3, t)).toBe(true);
expect(wfs.scheduleDue({ type: 'schedule', every: 'fortnight' }, 0, t)).toBe(false);
expect(wfs.scheduleDue(null, 0, t)).toBe(false);
});
});