groceries: nightly AH/Jumbo catalogue import, unit-price normalisation, product groups and compare UI
All checks were successful
Deploy production / deploy (push) Successful in 2m48s

This commit is contained in:
2026-10-24 14:01:25 +00:00
parent 8dde5aa302
commit 427f4ed095
20 changed files with 34891 additions and 699 deletions

44
src/lib/groceries.test.ts Normal file
View File

@@ -0,0 +1,44 @@
import { describe, expect, it } from 'vitest';
import { cheapest, formatAmount, formatUnitPrice, sortByPrice, type PricedItem } from './groceries';
const it_ = (
id: number,
storeId: number,
price: number,
unit: number,
promo: number | null = null
): PricedItem => ({
id,
storeId,
priceCents: price,
promoPriceCents: promo,
unitPriceCents: unit,
promoUnitPriceCents: promo == null ? null : Math.round((unit * promo) / price),
available: true
});
describe('grocery price helpers', () => {
const items = [
it_(1, 1, 119, 119),
it_(2, 1, 165, 110),
it_(3, 2, 115, 115),
it_(4, 2, 200, 200, 100)
];
it('finds the cheapest by unit price, promos included', () => {
const c = cheapest(items, 'unit');
expect(c.overall).toBe(4);
expect([...c.perStore].sort()).toEqual([2, 4]);
});
it('finds the cheapest by pack price', () => {
expect(cheapest(items, 'pack').overall).toBe(4);
expect(cheapest(items.slice(0, 3), 'pack').overall).toBe(3);
});
it('sorts by unit price', () => {
expect(sortByPrice(items, 'unit').map((i) => i.id)).toEqual([4, 2, 3, 1]);
});
it('formats', () => {
expect(formatUnitPrice(250, 'kg')).toMatch(/2,50\/kg$/);
expect(formatAmount(1.98, 'l')).toBe('1,98 L');
expect(formatUnitPrice(null, 'kg')).toBeNull();
});
});