feat(planner): J4 swap flow — action sheet + easiest-first suggestions #45
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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".
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user