import { describe, it, expect } from 'vitest'; import { cronToFormState } from './cronForm'; /** * The backend's convertToCron (src/scheduler.ts) — mirrored here so the test * asserts a true round-trip: editing a saved schedule and saving it back must * not change the cron expression. */ function convertToCron(f: ReturnType): string { const m = f.minute ?? 0; const h = f.hour ?? 0; switch (f.scheduleType) { case 'daily': return `${m} ${h} * * *`; case 'weekly': return `${m} ${h} * * ${f.dayOfWeek ?? 0}`; case 'monthly': return `${m} ${h} ${f.dayOfMonth ?? 1} * *`; case 'cron': return f.cronExpression; case 'once': return 'once'; } } describe('cronToFormState', () => { it('classifies simple presets (daily/weekly/monthly)', () => { expect(cronToFormState('0 9 * * *')).toMatchObject({ scheduleType: 'daily', hour: 9, minute: 0 }); expect(cronToFormState('30 14 * * *')).toMatchObject({ scheduleType: 'daily', hour: 14, minute: 30 }); expect(cronToFormState('0 9 * * 3')).toMatchObject({ scheduleType: 'weekly', hour: 9, minute: 0, dayOfWeek: 3 }); expect(cronToFormState('0 9 15 * *')).toMatchObject({ scheduleType: 'monthly', hour: 9, minute: 0, dayOfMonth: 15 }); }); it("keeps custom expressions as 'cron' and preserves the raw expression", () => { for (const expr of [ '0 9 * * 1-5', '*/30 * * * *', '0 */2 * * *', '0 9,17 * * *', '15 9 1,15 * *', '0 9 1 6 *', '0 9 * * 7', // Sunday-as-7 is out of the editor's 0-6 select range '0 9 * * *', // double space → 6 tokens, not a clean preset ]) { const f = cronToFormState(expr); expect(f.scheduleType).toBe('cron'); expect(f.cronExpression).toBe(expr); } }); it('classifies Sunday-as-0 weekly', () => { expect(cronToFormState('0 9 * * 0')).toMatchObject({ scheduleType: 'weekly', dayOfWeek: 0 }); }); it('never produces NaN fields for custom expressions', () => { const f = cronToFormState('0 9 * * 1-5'); expect(Number.isNaN(f.hour)).toBe(false); expect(Number.isNaN(f.minute)).toBe(false); expect(Number.isNaN(f.dayOfWeek)).toBe(false); expect(Number.isNaN(f.dayOfMonth)).toBe(false); }); it("maps 'once' to the once type", () => { expect(cronToFormState('once')).toMatchObject({ scheduleType: 'once' }); }); it('treats a malformed (non 5-field) string as a raw cron expression', () => { expect(cronToFormState('garbage')).toMatchObject({ scheduleType: 'cron', cronExpression: 'garbage' }); }); it('round-trips losslessly through the backend convertToCron', () => { const cases = [ '0 9 * * *', '30 14 * * *', '0 9 * * 3', '0 9 15 * *', '0 9 * * 1-5', '*/30 * * * *', '0 */2 * * *', '0 9,17 * * *', '15 9 1,15 * *', '0 9 1 6 *', 'once', ]; for (const expr of cases) { expect(convertToCron(cronToFormState(expr))).toBe(expr); } }); });