Files
tessera-ctl/apps/web/src/lib/dashboard-images-api.test.ts
T
schalli c080580459 feat(quick-260921-pi9): Bilderrahmen-Widget - Diashow mit Grossansicht, Bildverwaltung in den Einstellungen
- 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>
2026-09-21 18:56:02 +02:00

93 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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');
});
});