gitshark

Clone repository

git clone https://git.vilip.de/git/vilip/taskflow.git
git clone git@git.vilip.de:vilip/taskflow.git
import {
  DEFAULT_REMINDER_TIME,
  WEEKDAY_LABELS,
  WEEKDAY_DISPLAY_ORDER,
  buildReminderBody,
  buildReminderOccurrences,
  countTasksDueOn,
  createUniformSchedule,
  describeReminderSchedule,
  formatReminderTime,
  getReminderTimeForDate,
  isValidReminderSchedule,
  isValidReminderTime,
  withReminderTimeForWeekday,
} from '../reminder';

describe('buildReminderBody', () => {
  it('reports when there are no tasks for today', () => {
    expect(buildReminderBody(0)).toBe('No tasks scheduled for today');
  });

  it('uses the singular form for exactly one task', () => {
    expect(buildReminderBody(1)).toBe('You have 1 task scheduled for today');
  });

  it('uses the plural form for multiple tasks', () => {
    expect(buildReminderBody(5)).toBe('You have 5 tasks scheduled for today');
  });

  it('treats negative counts as zero', () => {
    expect(buildReminderBody(-3)).toBe('No tasks scheduled for today');
  });
});

describe('formatReminderTime', () => {
  it('zero-pads hours and minutes (24h format)', () => {
    expect(formatReminderTime({ hour: 8, minute: 5 })).toBe('08:05');
  });

  it('formats the noon default', () => {
    expect(formatReminderTime(DEFAULT_REMINDER_TIME)).toBe('12:00');
  });

  it('formats late evening times', () => {
    expect(formatReminderTime({ hour: 23, minute: 59 })).toBe('23:59');
  });
});

describe('countTasksDueOn', () => {
  const day = new Date(2026, 7, 17);

  it('counts open tasks due anywhere within that day', () => {
    const tasks = [
      { dueDate: new Date(2026, 7, 17, 0, 0).getTime() },
      { dueDate: new Date(2026, 7, 17, 23, 59).getTime() },
      { dueDate: new Date(2026, 7, 18, 0, 0).getTime() },
      { dueDate: new Date(2026, 7, 16, 23, 59).getTime() },
    ];
    expect(countTasksDueOn(tasks, day)).toBe(2);
  });

  it('ignores completed tasks and tasks without a due date', () => {
    const tasks = [
      { dueDate: new Date(2026, 7, 17, 9, 0).getTime() },
      { dueDate: new Date(2026, 7, 17, 9, 0).getTime(), completed: true },
      { completed: false },
    ];
    expect(countTasksDueOn(tasks, day)).toBe(1);
  });
});

describe('createUniformSchedule', () => {
  it('gives every weekday the same time', () => {
    const schedule = createUniformSchedule({ hour: 7, minute: 30 });

    expect(schedule).toHaveLength(7);
    expect(schedule.every(time => formatReminderTime(time) === '07:30')).toBe(true);
  });

  it('does not share one time object between weekdays', () => {
    const schedule = createUniformSchedule({ hour: 7, minute: 30 });
    expect(schedule[0]).not.toBe(schedule[1]);
  });
});

describe('withReminderTimeForWeekday', () => {
  it('changes only the given weekday', () => {
    const schedule = createUniformSchedule({ hour: 12, minute: 0 });
    const updated = withReminderTimeForWeekday(schedule, 1, { hour: 6, minute: 45 });

    expect(formatReminderTime(updated[1])).toBe('06:45');
    expect(updated.filter(time => formatReminderTime(time) === '12:00')).toHaveLength(6);
  });

  it('leaves the original schedule untouched', () => {
    const schedule = createUniformSchedule({ hour: 12, minute: 0 });
    withReminderTimeForWeekday(schedule, 1, { hour: 6, minute: 45 });

    expect(formatReminderTime(schedule[1])).toBe('12:00');
  });
});

describe('getReminderTimeForDate', () => {
  it('picks the time configured for that weekday', () => {
    const schedule = createUniformSchedule({ hour: 12, minute: 0 });
    schedule[1] = { hour: 6, minute: 45 };

    // 2026-08-17 is a Monday, 2026-08-18 a Tuesday.
    expect(formatReminderTime(getReminderTimeForDate(schedule, new Date(2026, 7, 17)))).toBe('06:45');
    expect(formatReminderTime(getReminderTimeForDate(schedule, new Date(2026, 7, 18)))).toBe('12:00');
  });
});

describe('isValidReminderSchedule', () => {
  it('accepts a schedule of seven valid times', () => {
    expect(isValidReminderSchedule(createUniformSchedule(DEFAULT_REMINDER_TIME))).toBe(true);
  });

  it('rejects a schedule that does not cover every weekday', () => {
    expect(isValidReminderSchedule([{ hour: 8, minute: 0 }])).toBe(false);
  });

  it('rejects a schedule holding an invalid time', () => {
    const schedule = createUniformSchedule(DEFAULT_REMINDER_TIME);
    schedule[3] = { hour: 24, minute: 0 };
    expect(isValidReminderSchedule(schedule)).toBe(false);
  });

  it('rejects non-arrays', () => {
    expect(isValidReminderSchedule({ hour: 8, minute: 0 } as never)).toBe(false);
  });
});

describe('describeReminderSchedule', () => {
  it('names the time when every day shares it', () => {
    expect(describeReminderSchedule(createUniformSchedule({ hour: 9, minute: 0 }))).toBe('09:00 every day');
  });

  it('says so when the days differ', () => {
    const schedule = createUniformSchedule({ hour: 9, minute: 0 });
    schedule[6] = { hour: 11, minute: 0 };
    expect(describeReminderSchedule(schedule)).toBe('at a time set per weekday');
  });
});

describe('weekday display order', () => {
  it('starts the week on Monday and ends on Sunday', () => {
    expect(WEEKDAY_DISPLAY_ORDER.map(day => WEEKDAY_LABELS[day])).toEqual([
      'Monday',
      'Tuesday',
      'Wednesday',
      'Thursday',
      'Friday',
      'Saturday',
      'Sunday',
    ]);
  });
});

describe('buildReminderOccurrences', () => {
  const schedule = createUniformSchedule({ hour: 12, minute: 0 });

  it('starts today when the reminder time is still ahead', () => {
    const now = new Date(2026, 7, 17, 9, 30);
    const occurrences = buildReminderOccurrences([], schedule, { now, days: 2 });

    expect(occurrences.map(o => o.triggerAt)).toEqual([
      new Date(2026, 7, 17, 12, 0),
      new Date(2026, 7, 18, 12, 0),
    ]);
  });

  it('skips today when the reminder time has already passed', () => {
    const now = new Date(2026, 7, 17, 13, 0);
    const occurrences = buildReminderOccurrences([], schedule, { now, days: 2 });

    expect(occurrences.map(o => o.triggerAt)).toEqual([new Date(2026, 7, 18, 12, 0)]);
  });

  it('fires each day at the time configured for its weekday', () => {
    const perDay = createUniformSchedule({ hour: 12, minute: 0 });
    perDay[1] = { hour: 6, minute: 30 }; // Monday
    perDay[2] = { hour: 20, minute: 15 }; // Tuesday
    perDay[3] = { hour: 9, minute: 0 }; // Wednesday

    const now = new Date(2026, 7, 17, 5, 0);
    const occurrences = buildReminderOccurrences([], perDay, { now, days: 3 });

    expect(occurrences.map(o => o.triggerAt)).toEqual([
      new Date(2026, 7, 17, 6, 30),
      new Date(2026, 7, 18, 20, 15),
      new Date(2026, 7, 19, 9, 0),
    ]);
  });

  it('skips only today when today’s own time has passed', () => {
    const perDay = createUniformSchedule({ hour: 12, minute: 0 });
    perDay[1] = { hour: 6, minute: 30 }; // Monday, already over
    perDay[2] = { hour: 20, minute: 15 }; // Tuesday

    const now = new Date(2026, 7, 17, 7, 0);
    const occurrences = buildReminderOccurrences([], perDay, { now, days: 2 });

    expect(occurrences.map(o => o.triggerAt)).toEqual([new Date(2026, 7, 18, 20, 15)]);
  });

  it('counts the tasks of each day separately', () => {
    const now = new Date(2026, 7, 17, 9, 0);
    const tasks = [
      { dueDate: new Date(2026, 7, 17, 15, 0).getTime() },
      { dueDate: new Date(2026, 7, 18, 8, 0).getTime() },
      { dueDate: new Date(2026, 7, 18, 20, 0).getTime() },
      { dueDate: new Date(2026, 7, 18, 20, 0).getTime(), completed: true },
    ];

    const occurrences = buildReminderOccurrences(tasks, schedule, { now, days: 3 });

    expect(occurrences.map(o => o.taskCount)).toEqual([1, 2, 0]);
    expect(occurrences.map(o => o.body)).toEqual([
      'You have 1 task scheduled for today',
      'You have 2 tasks scheduled for today',
      'No tasks scheduled for today',
    ]);
  });
});

describe('isValidReminderTime', () => {
  it('accepts valid times', () => {
    expect(isValidReminderTime({ hour: 0, minute: 0 })).toBe(true);
    expect(isValidReminderTime({ hour: 23, minute: 59 })).toBe(true);
  });

  it('rejects out-of-range or non-integer values', () => {
    expect(isValidReminderTime({ hour: 24, minute: 0 })).toBe(false);
    expect(isValidReminderTime({ hour: -1, minute: 0 })).toBe(false);
    expect(isValidReminderTime({ hour: 12, minute: 60 })).toBe(false);
    expect(isValidReminderTime({ hour: 12.5, minute: 0 })).toBe(false);
  });
});

Keyboard shortcuts

?Show this help
g hGo home
EscClose dialog