schedule.test.ts 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. import { describe, expect, it } from 'vitest'
  2. import type { ScheduleRule } from './types'
  3. import { dueTimeRules, fireTimesBetween, nextFireTime, parseTime } from './schedule'
  4. function rule(id: string, time: string, extra: Partial<ScheduleRule> = {}): ScheduleRule {
  5. return {
  6. id,
  7. name: id,
  8. enabled: true,
  9. trigger: { type: 'time', time, days: [] },
  10. action: { type: 'stop' },
  11. screen: '*',
  12. ...extra
  13. }
  14. }
  15. // 2026-09-21 is a Monday
  16. const at = (day: number, hours: number, minutes = 0) => new Date(2026, 8, day, hours, minutes)
  17. describe('parseTime', () => {
  18. it('reads HH:MM and rejects anything else', () => {
  19. expect(parseTime('08:30')).toEqual({ hours: 8, minutes: 30 })
  20. expect(parseTime('8:05')).toEqual({ hours: 8, minutes: 5 })
  21. expect(parseTime('24:00')).toBeNull()
  22. expect(parseTime('12:60')).toBeNull()
  23. expect(parseTime('noon')).toBeNull()
  24. })
  25. })
  26. describe('fireTimesBetween', () => {
  27. it('excludes the start and includes the end of the window', () => {
  28. const r = rule('a', '08:00')
  29. expect(fireTimesBetween(r, at(21, 8), at(22, 8))).toEqual([at(22, 8)])
  30. })
  31. it('only fires on the picked days', () => {
  32. const weekdays = rule('a', '09:00', { trigger: { type: 'time', time: '09:00', days: [1, 2, 3, 4, 5] } })
  33. // Saturday the 26th and Sunday the 27th are skipped
  34. expect(fireTimesBetween(weekdays, at(25, 10), at(28, 10))).toEqual([at(28, 9)])
  35. })
  36. it('ignores theme rules', () => {
  37. const theme = rule('a', '', { trigger: { type: 'theme', theme: 'dark' } })
  38. expect(fireTimesBetween(theme, at(21, 0), at(28, 0))).toEqual([])
  39. })
  40. })
  41. describe('dueTimeRules', () => {
  42. it('keeps only the latest rule per screen after a long gap', () => {
  43. const morning = rule('morning', '08:00')
  44. const evening = rule('evening', '20:00')
  45. expect(dueTimeRules([morning, evening], at(21, 19), at(22, 9)).map((r) => r.id)).toEqual(['morning'])
  46. })
  47. it('treats screens separately, but an all-screens rule replaces older per-screen ones', () => {
  48. const left = rule('left', '07:00', { screen: 'DP-1' })
  49. const right = rule('right', '07:30', { screen: 'DP-2' })
  50. const all = rule('all', '07:15')
  51. expect(dueTimeRules([left, right], at(21, 6), at(21, 8)).map((r) => r.id).sort()).toEqual(['left', 'right'])
  52. expect(dueTimeRules([left, all, right], at(21, 6), at(21, 8)).map((r) => r.id).sort()).toEqual(['all', 'right'])
  53. })
  54. it('skips disabled rules', () => {
  55. expect(dueTimeRules([rule('a', '08:00', { enabled: false })], at(21, 7), at(21, 9))).toEqual([])
  56. })
  57. })
  58. describe('nextFireTime', () => {
  59. it('finds the next matching day', () => {
  60. const sundays = rule('a', '10:00', { trigger: { type: 'time', time: '10:00', days: [0] } })
  61. expect(nextFireTime(sundays, at(21, 12))).toEqual(at(27, 10))
  62. })
  63. })