import { beforeEach, describe, expect, it, vi } from 'vitest' import { triggerDownload } from './triggerDownload' describe('triggerDownload', () => { const fetchMock = vi.fn() const openMock = vi.fn() const createObjectURLMock = vi.fn() const revokeObjectURLMock = vi.fn() const clickMock = vi .spyOn(HTMLAnchorElement.prototype, 'click') .mockImplementation(() => undefined) beforeEach(() => { vi.stubGlobal('fetch', fetchMock) vi.spyOn(window, 'open').mockImplementation(openMock) Object.defineProperty(URL, 'createObjectURL', { configurable: true, value: createObjectURLMock, }) Object.defineProperty(URL, 'revokeObjectURL', { configurable: true, value: revokeObjectURLMock, }) fetchMock.mockReset() openMock.mockReset() createObjectURLMock.mockReset() revokeObjectURLMock.mockReset() clickMock.mockClear() }) it('downloads a successful response with the supplied filename', async () => { const blob = new Blob(['file']) fetchMock.mockResolvedValue({ ok: true, blob: () => Promise.resolve(blob), }) createObjectURLMock.mockReturnValue('blob:attachment') const appendSpy = vi.spyOn(document.body, 'appendChild') const removeSpy = vi.spyOn(document.body, 'removeChild') await expect( triggerDownload('https://cdn.example.com/file.pdf', 'report.pdf') ).resolves.toBeUndefined() expect(fetchMock).toHaveBeenCalledWith('https://cdn.example.com/file.pdf', { mode: 'cors', }) expect(createObjectURLMock).toHaveBeenCalledWith(blob) expect(clickMock).toHaveBeenCalledOnce() expect(revokeObjectURLMock).toHaveBeenCalledWith('blob:attachment') const anchor = appendSpy.mock.calls.at(-1)?.[0] as HTMLAnchorElement expect(anchor.href).toBe('blob:attachment') expect(anchor.download).toBe('report.pdf') expect(anchor.style.display).toBe('none') expect(removeSpy).toHaveBeenCalledWith(anchor) }) it('uses the URL filename when no filename is supplied', async () => { fetchMock.mockResolvedValue({ ok: true, blob: () => Promise.resolve(new Blob()), }) createObjectURLMock.mockReturnValue('blob:attachment') const appendSpy = vi.spyOn(document.body, 'appendChild') await triggerDownload('https://cdn.example.com/a/My%20file.pdf') expect( (appendSpy.mock.calls.at(-1)?.[0] as HTMLAnchorElement).download ).toBe('My file.pdf') }) it.each(['a non-ok response', 'a rejected fetch'])( 'falls back to a new window for %s', async (label) => { if (label === 'a non-ok response') { fetchMock.mockResolvedValue({ ok: false, status: 404 }) } else { fetchMock.mockRejectedValue(new Error('offline')) } openMock.mockReturnValue({}) await expect( triggerDownload('https://cdn.example.com/file.pdf') ).resolves.toBeUndefined() expect(openMock).toHaveBeenCalledWith( 'https://cdn.example.com/file.pdf', '_blank', 'noopener,noreferrer' ) } ) it('uses a raw-url anchor when the popup is blocked', async () => { fetchMock.mockRejectedValue(new Error('offline')) openMock.mockReturnValue(null) const appendSpy = vi.spyOn(document.body, 'appendChild') const removeSpy = vi.spyOn(document.body, 'removeChild') await expect( triggerDownload('https://cdn.example.com/file.pdf') ).resolves.toBeUndefined() const anchor = appendSpy.mock.calls.at(-1)?.[0] as HTMLAnchorElement expect(anchor.href).toBe('https://cdn.example.com/file.pdf') expect(anchor.download).toBe('file.pdf') expect(anchor.target).toBe('_blank') expect(anchor.rel).toBe('noopener noreferrer') expect(removeSpy).toHaveBeenCalledWith(anchor) }) })