197 lines
5.5 KiB
TypeScript
197 lines
5.5 KiB
TypeScript
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);
|
||
});
|
||
});
|