feat(client): PhotoUploadStore mit idle/loading/success/error
This commit is contained in:
87
tests/unit/photo-upload-store.test.ts
Normal file
87
tests/unit/photo-upload-store.test.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { PhotoUploadStore } from '../../src/lib/client/photo-upload.svelte';
|
||||
|
||||
const validRecipe = {
|
||||
id: null,
|
||||
title: 'T',
|
||||
description: 'D',
|
||||
source_url: null,
|
||||
source_domain: null,
|
||||
image_path: null,
|
||||
servings_default: null,
|
||||
servings_unit: null,
|
||||
prep_time_min: null,
|
||||
cook_time_min: null,
|
||||
total_time_min: null,
|
||||
cuisine: null,
|
||||
category: null,
|
||||
ingredients: [],
|
||||
steps: [{ position: 1, text: 'S' }],
|
||||
tags: []
|
||||
};
|
||||
|
||||
function fakeFetch(responses: Response[]): typeof fetch {
|
||||
let i = 0;
|
||||
return vi.fn(async () => responses[i++]) as unknown as typeof fetch;
|
||||
}
|
||||
|
||||
function mkFile(): File {
|
||||
return new File([new Uint8Array([1, 2, 3])], 'x.jpg', { type: 'image/jpeg' });
|
||||
}
|
||||
|
||||
describe('PhotoUploadStore', () => {
|
||||
it('starts in idle', () => {
|
||||
const s = new PhotoUploadStore();
|
||||
expect(s.status).toBe('idle');
|
||||
});
|
||||
|
||||
it('transitions loading → success on happy path', async () => {
|
||||
const s = new PhotoUploadStore({
|
||||
fetchImpl: fakeFetch([
|
||||
new Response(JSON.stringify({ recipe: validRecipe }), { status: 200 })
|
||||
])
|
||||
});
|
||||
await s.upload(mkFile());
|
||||
expect(s.status).toBe('success');
|
||||
expect(s.recipe?.title).toBe('T');
|
||||
});
|
||||
|
||||
it('transitions to error with code on 422', async () => {
|
||||
const s = new PhotoUploadStore({
|
||||
fetchImpl: fakeFetch([
|
||||
new Response(
|
||||
JSON.stringify({ code: 'NO_RECIPE_IN_IMAGE', message: 'nope' }),
|
||||
{ status: 422 }
|
||||
)
|
||||
])
|
||||
});
|
||||
await s.upload(mkFile());
|
||||
expect(s.status).toBe('error');
|
||||
expect(s.errorCode).toBe('NO_RECIPE_IN_IMAGE');
|
||||
});
|
||||
|
||||
it('reset() brings store back to idle', async () => {
|
||||
const s = new PhotoUploadStore({
|
||||
fetchImpl: fakeFetch([new Response('{"code":"X"}', { status: 503 })])
|
||||
});
|
||||
await s.upload(mkFile());
|
||||
expect(s.status).toBe('error');
|
||||
s.reset();
|
||||
expect(s.status).toBe('idle');
|
||||
expect(s.errorCode).toBeNull();
|
||||
expect(s.lastFile).toBeNull();
|
||||
});
|
||||
|
||||
it('retry re-uploads lastFile', async () => {
|
||||
const s = new PhotoUploadStore({
|
||||
fetchImpl: fakeFetch([
|
||||
new Response('{"code":"X"}', { status: 503 }),
|
||||
new Response(JSON.stringify({ recipe: validRecipe }), { status: 200 })
|
||||
])
|
||||
});
|
||||
await s.upload(mkFile());
|
||||
expect(s.status).toBe('error');
|
||||
await s.retry();
|
||||
expect(s.status).toBe('success');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user