c080580459
- picture-frame-config.ts: Eintragstyp als Vereinigung (upload | url) in EINER geordneten Liste, resolvePictureFrameConfig laesst alles ausser https weg (T-PI9-07), Intervall 0 oder 5..3600 s, pickNextIndex (Zufall nie dasselbe) - dashboard-images-api.ts: Upload als FormData-Feld image ohne eigenen Content-Type, 413 -> deutsche Meldung, Proxy-Pfad fuer <img src> - PictureFrameWidget: Leerzustand, <img referrerPolicy=no-referrer> (Browser laedt Fremdbilder, Server nie), object-contain/cover, Unterschrift-Streifen, Wechsel per Timer mit Raeumung, kaputte Bilder verlassen den Umlauf, Grossansicht nur ausserhalb des Bearbeitungsmodus mit Fokus-Rueckgabe und Pause des Wechsels; im Bearbeitungsmodus kein Knopf (Karte bleibt Griff) - PictureFrameConfigForm im WidgetSettingsPanel: Ausschnitt, Intervall, Reihenfolge, Liste mit Vorschau/Unterschrift/Pfeilen/Entfernen (Upload wird auch serverseitig geloescht), Datei hochladen, https-Adresse hinzufuegen - Registry (4x4 min, 8x8 Vorgabe), Katalog, Seite, Uebersetzungen de/en (widgets.pictureFrame, 28 Schluessel), bestehende Tests auf acht Typen Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
93 lines
3.8 KiB
TypeScript
93 lines
3.8 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||
import {
|
||
dashboardImageSrc,
|
||
deleteDashboardImage,
|
||
fetchDashboardImages,
|
||
uploadDashboardImage,
|
||
} from './dashboard-images-api';
|
||
|
||
/**
|
||
* dashboard-images-api.test — NEU (quick-260921-pi9). Die vier Aufrufe des
|
||
* Bilderrahmen-Widgets gegen /dashboard/images: FormData-Feld `image` ohne
|
||
* eigenen Content-Type, Cookie immer mit, 413 und 400 mit deutschen
|
||
* Meldungen, Proxy-Pfad fuer <img src>.
|
||
*/
|
||
const { mockFetch } = vi.hoisted(() => ({ mockFetch: vi.fn() }));
|
||
|
||
beforeEach(() => {
|
||
mockFetch.mockReset();
|
||
vi.stubGlobal('fetch', mockFetch);
|
||
});
|
||
|
||
afterEach(() => {
|
||
vi.unstubAllGlobals();
|
||
});
|
||
|
||
describe('dashboard-images-api (quick-260921-pi9)', () => {
|
||
it('Test 1: uploadDashboardImage sendet POST mit FormData-Feld image, credentials include, ohne Content-Type von Hand', async () => {
|
||
mockFetch.mockResolvedValue(
|
||
new Response(JSON.stringify({ id: 'img-1', originalName: 'a.png', mimeType: 'image/png', size: 3 }), {
|
||
status: 201,
|
||
}),
|
||
);
|
||
const file = new File([new Uint8Array([1, 2, 3])], 'a.png', { type: 'image/png' });
|
||
const result = await uploadDashboardImage(file);
|
||
expect(result.id).toBe('img-1');
|
||
|
||
const [url, init] = mockFetch.mock.calls[0] as [string, RequestInit];
|
||
expect(String(url).endsWith('/dashboard/images')).toBe(true);
|
||
expect(init.method).toBe('POST');
|
||
expect(init.credentials).toBe('include');
|
||
expect(init.headers).toBeUndefined();
|
||
const body = init.body as FormData;
|
||
expect(body.get('image')).toBeInstanceOf(File);
|
||
expect((body.get('image') as File).name).toBe('a.png');
|
||
});
|
||
|
||
it('Test 2: Status 413 -> deutsche Meldung "zu gross"', async () => {
|
||
mockFetch.mockResolvedValue(new Response('{"message":"File too large"}', { status: 413 }));
|
||
await expect(uploadDashboardImage(new File([1], 'x.png'))).rejects.toThrow(
|
||
'Die Datei ist zu groß – erlaubt sind höchstens 5 MB.',
|
||
);
|
||
});
|
||
|
||
it('Test 3: Status 400 mit { message } -> genau diese Meldung; sonst allgemeine Meldung', async () => {
|
||
mockFetch.mockResolvedValue(
|
||
new Response(JSON.stringify({ message: 'Nur Bilder im Format PNG, JPEG, GIF oder WebP sind erlaubt.' }), {
|
||
status: 400,
|
||
}),
|
||
);
|
||
await expect(uploadDashboardImage(new File([1], 'x.png'))).rejects.toThrow(
|
||
'Nur Bilder im Format PNG, JPEG, GIF oder WebP sind erlaubt.',
|
||
);
|
||
|
||
mockFetch.mockResolvedValue(new Response('kaputt', { status: 500 }));
|
||
await expect(uploadDashboardImage(new File([1], 'x.png'))).rejects.toThrow(
|
||
'Das Bild konnte nicht hochgeladen werden.',
|
||
);
|
||
});
|
||
|
||
it('Test 4: fetchDashboardImages (GET) und deleteDashboardImage (DELETE, Kennung kodiert) mit Cookie', async () => {
|
||
mockFetch.mockResolvedValue(new Response('[]', { status: 200 }));
|
||
await expect(fetchDashboardImages()).resolves.toEqual([]);
|
||
const [listUrl, listInit] = mockFetch.mock.calls[0] as [string, RequestInit];
|
||
expect(String(listUrl).endsWith('/dashboard/images')).toBe(true);
|
||
expect(listInit.credentials).toBe('include');
|
||
|
||
mockFetch.mockResolvedValue(new Response('{"id":"a b"}', { status: 200 }));
|
||
await deleteDashboardImage('a b');
|
||
const [delUrl, delInit] = mockFetch.mock.calls[1] as [string, RequestInit];
|
||
expect(String(delUrl).endsWith('/dashboard/images/a%20b')).toBe(true);
|
||
expect(delInit.method).toBe('DELETE');
|
||
expect(delInit.credentials).toBe('include');
|
||
|
||
mockFetch.mockResolvedValue(new Response('', { status: 404 }));
|
||
await expect(deleteDashboardImage('weg')).rejects.toThrow();
|
||
});
|
||
|
||
it('Test 5: dashboardImageSrc kodiert die Kennung im Proxy-Pfad', () => {
|
||
expect(dashboardImageSrc('a b')).toBe('/api-proxy/dashboard/images/a%20b');
|
||
expect(dashboardImageSrc('img-1')).toBe('/api-proxy/dashboard/images/img-1');
|
||
});
|
||
});
|