import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { captureScreenshot, sendBugReport } from './bug-report-api'; const { mockToPng } = vi.hoisted(() => ({ mockToPng: vi.fn() })); vi.mock('html-to-image', () => ({ toPng: mockToPng })); /** * bug-report-api.test — NEU (quick-260918-gza). Bisher pruefte nur der * Komponententest (`bug-report-button.test.tsx`) die FormData von * `sendBugReport` — die reinen Nutzlastfelder (insbesondere die vier neuen * Herkunftsfelder) gehoeren aber an die Funktion selbst, unabhaengig vom * Dialog. Drei Tests: Desktop-Nutzlast, Browser-Nutzlast, Netzwerk-/ * HTTP-Fehler. */ const { mockFetch } = vi.hoisted(() => ({ mockFetch: vi.fn() })); const basePayload = { description: 'x', page: '/a', webVersion: 'v1', webChannel: 'beta', webCommit: 'c', userAgent: 'UA', viewport: '1x1', clientTime: 't', errors: ['e1', 'e2'], screenshot: null, }; beforeEach(() => { mockFetch.mockReset(); mockFetch.mockResolvedValue(new Response('{}', { status: 200 })); vi.stubGlobal('fetch', mockFetch); }); afterEach(() => { vi.unstubAllGlobals(); }); describe('sendBugReport (quick-260918-gza)', () => { it('Test 1: Desktop-Nutzlast — clientKind/clientOs/clientVersion/clientCommit im FormData, errors mehrfach, kein screenshot-Feld', async () => { const result = await sendBugReport({ ...basePayload, clientKind: 'desktop', clientOs: 'windows', clientVersion: '1.2.0', clientCommit: 'a6d1a64', }); expect(result).toEqual({ ok: true }); const [url, init] = mockFetch.mock.calls[0] as [string, RequestInit]; expect(String(url).endsWith('/bug-reports')).toBe(true); expect(init.credentials).toBe('include'); const body = init.body as FormData; expect(body.get('clientKind')).toBe('desktop'); expect(body.get('clientOs')).toBe('windows'); expect(body.get('clientVersion')).toBe('1.2.0'); expect(body.get('clientCommit')).toBe('a6d1a64'); expect(body.getAll('errors')).toEqual(['e1', 'e2']); expect(body.has('screenshot')).toBe(false); }); it('Test 2: Browser-Nutzlast — die drei uebrigen Client-Felder sind vorhanden, aber leer (nicht weggelassen)', async () => { await sendBugReport({ ...basePayload, clientKind: 'browser', clientOs: '', clientVersion: '', clientCommit: '', }); const body = (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as FormData; expect(body.get('clientKind')).toBe('browser'); expect(body.get('clientOs')).toBe(''); expect(body.get('clientVersion')).toBe(''); expect(body.get('clientCommit')).toBe(''); }); it('Test 3: Netzwerkfehler -> { ok: false, status: 0 }; HTTP-Fehler -> { ok: false, status }', async () => { mockFetch.mockRejectedValueOnce(new Error('offline')); await expect( sendBugReport({ ...basePayload, clientKind: 'browser', clientOs: '', clientVersion: '', clientCommit: '' }), ).resolves.toEqual({ ok: false, status: 0 }); mockFetch.mockResolvedValueOnce(new Response('', { status: 429 })); await expect( sendBugReport({ ...basePayload, clientKind: 'browser', clientOs: '', clientVersion: '', clientCommit: '' }), ).resolves.toEqual({ ok: false, status: 429 }); }); }); describe('captureScreenshot (Bild von fremder Adresse)', () => { beforeEach(() => { mockToPng.mockReset(); }); it('ersetzt nicht ladbare Bilder statt die Aufnahme abzubrechen', async () => { mockToPng.mockResolvedValue('data:image/png;base64,AAA'); await expect(captureScreenshot()).resolves.toBe('data:image/png;base64,AAA'); const opts = mockToPng.mock.calls[0][1]; expect(opts.imagePlaceholder).toMatch(/^data:image\/png;base64,/); expect(opts.onImageErrorHandler()).toBeUndefined(); }); it('versucht es ein zweites Mal ohne Bilder und Rahmen, wenn der erste Versuch scheitert', async () => { mockToPng .mockRejectedValueOnce(new Error('tainted')) .mockResolvedValueOnce('data:image/png;base64,BBB'); await expect(captureScreenshot()).resolves.toBe('data:image/png;base64,BBB'); expect(mockToPng).toHaveBeenCalledTimes(2); const filter = mockToPng.mock.calls[1][1].filter as (n: Node) => boolean; expect(filter(document.createElement('img'))).toBe(false); expect(filter(document.createElement('iframe'))).toBe(false); expect(filter(document.createElement('div'))).toBe(true); const ignored = document.createElement('div'); ignored.dataset.bugReportIgnore = 'true'; expect(filter(ignored)).toBe(false); }); it('liefert null, wenn auch der zweite Versuch scheitert', async () => { mockToPng.mockRejectedValue(new Error('kaputt')); await expect(captureScreenshot()).resolves.toBeNull(); }); });