diff --git a/frontend/src/lib/planner/week.test.ts b/frontend/src/lib/planner/week.test.ts index 0b919e6..8c433cf 100644 --- a/frontend/src/lib/planner/week.test.ts +++ b/frontend/src/lib/planner/week.test.ts @@ -6,7 +6,8 @@ import { weekDays, isToday, formatWeekRange, - formatDayLabel + formatDayLabel, + sortEasiestFirst } from './week'; describe('getWeekStart', () => { @@ -144,3 +145,52 @@ describe('formatDayLabel', () => { 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); + }); +}); diff --git a/frontend/src/lib/planner/week.ts b/frontend/src/lib/planner/week.ts index 69c8b76..6b31114 100644 --- a/frontend/src/lib/planner/week.ts +++ b/frontend/src/lib/planner/week.ts @@ -75,6 +75,25 @@ export function isToday(dateStr: string): boolean { return dateStr === todayStr; } +const EFFORT_ORDER: Record = { easy: 0, medium: 1, hard: 2 }; + +/** + * Returns a new array of recipes sorted easiest first (effort ASC, cookTimeMin ASC). + * Used for the J4 mid-week swap context — different from variety-first sorting in J2. + */ +export function sortEasiestFirst( + recipes: T[] +): T[] { + return [...recipes].sort((a, b) => { + const ea = a.effort != null ? (EFFORT_ORDER[a.effort] ?? 99) : 99; + const eb = b.effort != null ? (EFFORT_ORDER[b.effort] ?? 99) : 99; + if (ea !== eb) return ea - eb; + const ta = a.cookTimeMin ?? Infinity; + const tb = b.cookTimeMin ?? Infinity; + return ta - tb; + }); +} + /** * Formats a week range: "30. Mär – 5. Apr 2026". */