From 123d9812bc8c49f1e7808373c217f42b3d0a8c10 Mon Sep 17 00:00:00 2001 From: Knacky Date: Mon, 8 Jun 2026 18:20:29 +0200 Subject: [PATCH] test: cover Content-Disposition fallback in ExportEngagementButton --- frontend/tests/exports.test.ts | 67 ++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 frontend/tests/exports.test.ts diff --git a/frontend/tests/exports.test.ts b/frontend/tests/exports.test.ts new file mode 100644 index 0000000..368b285 --- /dev/null +++ b/frontend/tests/exports.test.ts @@ -0,0 +1,67 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import MockAdapter from 'axios-mock-adapter'; +import { apiClient } from '@/api/client'; +import { downloadEngagementExport } from '@/api/exports'; + +describe('downloadEngagementExport — Content-Disposition fallback', () => { + let mock: MockAdapter; + let capturedAnchor: HTMLAnchorElement | null = null; + + beforeEach(() => { + mock = new MockAdapter(apiClient); + capturedAnchor = null; + + globalThis.URL.createObjectURL = vi.fn().mockReturnValue('blob:fake-url'); + globalThis.URL.revokeObjectURL = vi.fn(); + + const origCreateElement = document.createElement.bind(document); + vi.spyOn(document, 'createElement').mockImplementation((tag: string) => { + const el = origCreateElement(tag); + if (tag === 'a') { + capturedAnchor = el as HTMLAnchorElement; + vi.spyOn(el as HTMLAnchorElement, 'click').mockImplementation(() => {}); + } + return el; + }); + + vi.spyOn(document.body, 'appendChild').mockImplementation((node) => node); + vi.spyOn(document.body, 'removeChild').mockImplementation((node) => node); + }); + + afterEach(() => { + mock.restore(); + vi.restoreAllMocks(); + }); + + it('uses fallback filename engagement-{id}.md when Content-Disposition is absent', async () => { + mock.onGet('/engagements/42/export').reply(200, new Blob(['# test']), { + 'content-type': 'text/markdown', + }); + + await downloadEngagementExport(42, 'md'); + + expect(capturedAnchor?.download).toBe('engagement-42.md'); + }); + + it('uses fallback filename engagement-{id}.csv when Content-Disposition is malformed (no filename=)', async () => { + mock.onGet('/engagements/7/export').reply(200, new Blob(['col1,col2']), { + 'content-type': 'text/csv', + 'content-disposition': 'attachment', + }); + + await downloadEngagementExport(7, 'csv'); + + expect(capturedAnchor?.download).toBe('engagement-7.csv'); + }); + + it('uses filename from Content-Disposition when header is well-formed', async () => { + mock.onGet('/engagements/5/export').reply(200, new Blob(['data']), { + 'content-type': 'application/pdf', + 'content-disposition': 'attachment; filename="engagement-5-slug-20260101.pdf"', + }); + + await downloadEngagementExport(5, 'pdf'); + + expect(capturedAnchor?.download).toBe('engagement-5-slug-20260101.pdf'); + }); +});