/** @vitest-environment jsdom */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { shareTryOn, tryOnFilename } from './share'; import { downloadImage } from '../imageUtils'; vi.mock('../imageUtils', () => ({ downloadImage: vi.fn(), })); const IMAGE = 'data:image/png;base64,aGVsbG8='; const request = { image: IMAGE, filename: 'virtual-tryon-shirt.png', title: 'My virtual try-on', url: '/products/linen-shirt', }; function stubNavigator(overrides: Record) { for (const [key, value] of Object.entries(overrides)) { Object.defineProperty(navigator, key, { value, configurable: true, writable: true }); } } function unstubNavigator(keys: string[]) { for (const key of keys) { Reflect.deleteProperty(navigator, key); } } beforeEach(() => { vi.mocked(downloadImage).mockClear(); }); afterEach(() => { unstubNavigator(['share', 'canShare', 'clipboard']); }); describe('sharing a try-on', () => { it('hands the render to the OS share sheet as a file, with no upload', async () => { const share = vi.fn().mockResolvedValue(undefined); const fetchSpy = vi.fn(); vi.stubGlobal('fetch', fetchSpy); stubNavigator({ share, canShare: () => true }); const outcome = await shareTryOn(request); expect(outcome).toBe('shared'); const payload = share.mock.calls[0][0]; expect(payload.files).toHaveLength(1); expect(payload.files[0].name).toBe('virtual-tryon-shirt.png'); expect(payload.files[0].type).toBe('image/png'); expect(fetchSpy).not.toHaveBeenCalled(); vi.unstubAllGlobals(); }); it('treats a closed share sheet as a dismissal, not a failure', async () => { const abort = Object.assign(new Error('cancelled'), { name: 'AbortError' }); stubNavigator({ share: vi.fn().mockRejectedValue(abort), canShare: () => true }); expect(await shareTryOn(request)).toBe('dismissed'); expect(downloadImage).not.toHaveBeenCalled(); }); it('shares the product link when the browser refuses files', async () => { const share = vi.fn().mockResolvedValue(undefined); stubNavigator({ share, canShare: () => false }); expect(await shareTryOn(request)).toBe('shared'); const payload = share.mock.calls[0][0]; expect(payload.files).toBeUndefined(); expect(payload.url).toContain('/products/linen-shirt'); }); it('falls back to a download when there is no Web Share API', async () => { expect(await shareTryOn(request)).toBe('downloaded'); expect(downloadImage).toHaveBeenCalledWith(IMAGE, 'virtual-tryon-shirt.png'); }); it('falls back to copying the link when even the download fails', async () => { vi.mocked(downloadImage).mockImplementationOnce(() => { throw new Error('no downloads here'); }); const writeText = vi.fn().mockResolvedValue(undefined); stubNavigator({ clipboard: { writeText } }); expect(await shareTryOn(request)).toBe('copied'); expect(writeText).toHaveBeenCalledWith(expect.stringContaining('/products/linen-shirt')); }); it('reports failure when nothing at all is available', async () => { vi.mocked(downloadImage).mockImplementationOnce(() => { throw new Error('no downloads here'); }); expect(await shareTryOn({ ...request, url: undefined })).toBe('failed'); }); }); describe('tryOnFilename', () => { it('slugifies the product name', () => { expect(tryOnFilename('Linen Shirt', 1234)).toBe('virtual-tryon-linen-shirt-1234.png'); }); it('reduces accents and punctuation to a safe filename', () => { expect(tryOnFilename('Maglietta à righe (blu)!', 1234)).toBe('virtual-tryon-maglietta-righe-blu-1234.png'); }); it('still names the file when the product has no title', () => { expect(tryOnFilename(undefined, 1234)).toBe('virtual-tryon-result-1234.png'); }); });