feat(planner): add sortEasiestFirst utility for J4 swap context

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 10:03:47 +02:00
parent dac83c70ea
commit 8756bf93d9
2 changed files with 70 additions and 1 deletions

View File

@@ -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);
});
});

View File

@@ -75,6 +75,25 @@ export function isToday(dateStr: string): boolean {
return dateStr === todayStr;
}
const EFFORT_ORDER: Record<string, number> = { 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<T extends { effort?: string | null; cookTimeMin?: number | null }>(
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".
*/