Files
kochwas/tests/unit/shopping-cart-store.test.ts
hsiegeln 1bd5dd106f
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m16s
feat(shopping): ShoppingCartStore (Client)
Svelte-5-Runes-Store mit uncheckedCount, recipeIds und loaded.
refresh() holt Snapshot via GET /api/shopping-list, addRecipe/
removeRecipe posten bzw. loeschen und refreshen anschliessend.
Bei Netzwerkfehler bleibt der letzte bekannte State erhalten.
2026-04-21 23:31:29 +02:00

74 lines
2.4 KiB
TypeScript

// @vitest-environment jsdom
import { describe, it, expect, vi } from 'vitest';
import { ShoppingCartStore } from '../../src/lib/client/shopping-cart.svelte';
type FetchMock = ReturnType<typeof vi.fn>;
function snapshotBody(opts: {
recipeIds?: number[];
uncheckedCount?: number;
}) {
return {
recipes: (opts.recipeIds ?? []).map((id) => ({
recipe_id: id, title: `R${id}`, image_path: null, servings: 4, servings_default: 4
})),
rows: [],
uncheckedCount: opts.uncheckedCount ?? 0
};
}
function makeFetch(responses: unknown[]): FetchMock {
const queue = [...responses];
return vi.fn(async () => ({
ok: true,
status: 200,
json: async () => queue.shift()
} as Response));
}
describe('ShoppingCartStore', () => {
it('refresh populates recipeIds and uncheckedCount', async () => {
const fetchImpl = makeFetch([snapshotBody({ recipeIds: [1, 2], uncheckedCount: 3 })]);
const store = new ShoppingCartStore(fetchImpl);
await store.refresh();
expect(store.uncheckedCount).toBe(3);
expect(store.isInCart(1)).toBe(true);
expect(store.isInCart(2)).toBe(true);
expect(store.isInCart(3)).toBe(false);
expect(store.loaded).toBe(true);
});
it('addRecipe posts then refreshes', async () => {
const fetchImpl = makeFetch([
{},
snapshotBody({ recipeIds: [42], uncheckedCount: 5 })
]);
const store = new ShoppingCartStore(fetchImpl);
await store.addRecipe(42);
expect(fetchImpl.mock.calls[0][0]).toBe('/api/shopping-list/recipe');
expect(fetchImpl.mock.calls[0][1]).toMatchObject({ method: 'POST' });
expect(store.isInCart(42)).toBe(true);
expect(store.uncheckedCount).toBe(5);
});
it('removeRecipe deletes then refreshes', async () => {
const fetchImpl = makeFetch([
{},
snapshotBody({ recipeIds: [], uncheckedCount: 0 })
]);
const store = new ShoppingCartStore(fetchImpl);
await store.removeRecipe(42);
expect(fetchImpl.mock.calls[0][0]).toBe('/api/shopping-list/recipe/42');
expect(fetchImpl.mock.calls[0][1]).toMatchObject({ method: 'DELETE' });
expect(store.uncheckedCount).toBe(0);
});
it('refresh keeps last known state on network error', async () => {
const fetchImpl = vi.fn().mockRejectedValue(new Error('offline'));
const store = new ShoppingCartStore(fetchImpl);
store.uncheckedCount = 7;
await store.refresh();
expect(store.uncheckedCount).toBe(7);
});
});