Files
mealprep/frontend/src/lib/planner/week.test.ts
2026-04-09 10:03:47 +02:00

197 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect, vi, afterEach } from 'vitest';
import {
getWeekStart,
prevWeek,
nextWeek,
weekDays,
isToday,
formatWeekRange,
formatDayLabel,
sortEasiestFirst
} from './week';
describe('getWeekStart', () => {
it('returns Monday for a Monday input', () => {
// 2026-03-30 is a Monday
const d = new Date('2026-03-30T12:00:00Z');
expect(getWeekStart(d)).toBe('2026-03-30');
});
it('returns Monday for a Wednesday input', () => {
// 2026-04-01 is a Wednesday → week starts 2026-03-30
const d = new Date('2026-04-01T12:00:00Z');
expect(getWeekStart(d)).toBe('2026-03-30');
});
it('returns Monday for a Sunday input (edge case — goes back 6 days)', () => {
// 2026-04-05 is a Sunday → week starts 2026-03-30
const d = new Date('2026-04-05T12:00:00Z');
expect(getWeekStart(d)).toBe('2026-03-30');
});
it('returns Monday for a Saturday input', () => {
// 2026-04-04 is a Saturday → week starts 2026-03-30
const d = new Date('2026-04-04T12:00:00Z');
expect(getWeekStart(d)).toBe('2026-03-30');
});
it('handles year boundary correctly (Dec 28 2025 → week starts Dec 22 2025)', () => {
const d = new Date('2025-12-28T12:00:00Z');
expect(getWeekStart(d)).toBe('2025-12-22');
});
});
describe('prevWeek', () => {
it('returns the Monday 7 days before', () => {
expect(prevWeek('2026-03-30')).toBe('2026-03-23');
});
it('handles month boundary', () => {
expect(prevWeek('2026-04-06')).toBe('2026-03-30');
});
it('handles year boundary', () => {
expect(prevWeek('2026-01-05')).toBe('2025-12-29');
});
});
describe('nextWeek', () => {
it('returns the Monday 7 days after', () => {
expect(nextWeek('2026-03-30')).toBe('2026-04-06');
});
it('handles month boundary', () => {
expect(nextWeek('2026-03-30')).toBe('2026-04-06');
});
it('handles year boundary', () => {
expect(nextWeek('2025-12-29')).toBe('2026-01-05');
});
});
describe('weekDays', () => {
it('returns exactly 7 dates', () => {
expect(weekDays('2026-03-30')).toHaveLength(7);
});
it('starts on the given weekStart', () => {
const days = weekDays('2026-03-30');
expect(days[0]).toBe('2026-03-30');
});
it('ends 6 days after weekStart', () => {
const days = weekDays('2026-03-30');
expect(days[6]).toBe('2026-04-05');
});
it('has consecutive daily dates', () => {
const days = weekDays('2026-03-30');
for (let i = 1; i < 7; i++) {
const prev = new Date(days[i - 1] + 'T00:00:00Z');
const curr = new Date(days[i] + 'T00:00:00Z');
expect(curr.getTime() - prev.getTime()).toBe(86400000);
}
});
it('handles month boundary correctly', () => {
const days = weekDays('2026-03-30');
expect(days[1]).toBe('2026-03-31');
expect(days[2]).toBe('2026-04-01');
});
});
describe('isToday', () => {
afterEach(() => {
vi.useRealTimers();
});
it('returns true for today (UTC)', () => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-04-03T10:00:00Z'));
expect(isToday('2026-04-03')).toBe(true);
});
it('returns false for yesterday', () => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-04-03T10:00:00Z'));
expect(isToday('2026-04-02')).toBe(false);
});
it('returns false for tomorrow', () => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-04-03T10:00:00Z'));
expect(isToday('2026-04-04')).toBe(false);
});
});
describe('formatWeekRange', () => {
it('returns a non-empty string', () => {
expect(formatWeekRange('2026-03-30')).toBeTruthy();
});
it('contains both start and end month info', () => {
const range = formatWeekRange('2026-03-30');
// Start is March 30, end is April 5 — range should span both months
expect(range).toContain('');
});
});
describe('formatDayLabel', () => {
it('returns a non-empty string', () => {
expect(formatDayLabel('2026-03-30')).toBeTruthy();
});
it('contains a comma separator', () => {
expect(formatDayLabel('2026-03-30')).toContain(',');
});
});
describe('sortEasiestFirst', () => {
it('sorts easy before medium before hard', () => {
const recipes = [
{ id: '1', name: 'Hard', effort: 'hard', cookTimeMin: 10 },
{ id: '2', name: 'Easy', effort: 'easy', cookTimeMin: 10 },
{ id: '3', name: 'Medium', effort: 'medium', cookTimeMin: 10 }
];
const sorted = sortEasiestFirst(recipes);
expect(sorted.map((r) => r.effort)).toEqual(['easy', 'medium', 'hard']);
});
it('sorts by cookTimeMin ascending within same effort', () => {
const recipes = [
{ id: '1', name: 'Slow Easy', effort: 'easy', cookTimeMin: 60 },
{ id: '2', name: 'Fast Easy', effort: 'easy', cookTimeMin: 15 }
];
const sorted = sortEasiestFirst(recipes);
expect(sorted[0].name).toBe('Fast Easy');
});
it('treats missing effort as after hard', () => {
const recipes = [
{ id: '1', name: 'No effort', effort: undefined, cookTimeMin: 5 },
{ id: '2', name: 'Hard', effort: 'hard', cookTimeMin: 5 }
];
const sorted = sortEasiestFirst(recipes);
expect(sorted[0].effort).toBe('hard');
});
it('treats missing cookTimeMin as after defined values', () => {
const recipes = [
{ id: '1', name: 'No time', effort: 'easy', cookTimeMin: undefined },
{ id: '2', name: 'Has time', effort: 'easy', cookTimeMin: 30 }
];
const sorted = sortEasiestFirst(recipes);
expect(sorted[0].name).toBe('Has time');
});
it('does not mutate the original array', () => {
const recipes = [
{ id: '1', name: 'Hard', effort: 'hard', cookTimeMin: 10 },
{ id: '2', name: 'Easy', effort: 'easy', cookTimeMin: 10 }
];
const original = [...recipes];
sortEasiestFirst(recipes);
expect(recipes[0].effort).toBe(original[0].effort);
});
});