/** @vitest-environment jsdom */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, cleanup, fireEvent, waitFor } from '@testing-library/react'; import { GalleryView } from './GalleryView'; import { downloadImage } from '../imageUtils'; import { en } from '../i18n/en'; import type { TryOnHistoryItem } from '../types'; // The only import of imageUtils in this tree is the share fallback. vi.mock('../imageUtils', () => ({ downloadImage: vi.fn() })); const NOW = new Date(2026, 6, 30, 12, 0, 0); function item(overrides: Partial = {}): TryOnHistoryItem { return { id: 'try-1', resultImage: 'data:image/png;base64,RESULT', clothingImage: 'data:image/png;base64,GARMENT', productTitle: 'Linen Shirt', productUrl: '/product/linen-shirt', createdAt: new Date(2026, 6, 30, 9, 0, 0).toISOString(), ...overrides, }; } function renderGallery(items: TryOnHistoryItem[], onDelete = vi.fn(), onBack = vi.fn()) { return { onDelete, onBack, ...render( ), }; } beforeEach(() => { vi.mocked(downloadImage).mockClear(); }); afterEach(() => { cleanup(); Reflect.deleteProperty(navigator, 'share'); Reflect.deleteProperty(navigator, 'canShare'); }); describe('an empty gallery', () => { it('explains what will show up there', () => { renderGallery([]); expect(screen.getByText(en.gallery.emptyTitle)).toBeDefined(); expect(screen.getByText(en.gallery.emptyMessage)).toBeDefined(); }); it('still offers the way back', () => { const { onBack } = renderGallery([]); fireEvent.click(screen.getByText(en.gallery.back)); expect(onBack).toHaveBeenCalledTimes(1); }); }); describe('a saved try-on', () => { it('shows the render, the product name and when it was made', () => { const { container } = renderGallery([item()]); expect(screen.getByText('Linen Shirt')).toBeDefined(); expect(screen.getByText(en.gallery.today)).toBeDefined(); const render_ = container.querySelector('.aisthetix-tryon-figure-image'); expect(render_?.getAttribute('src')).toBe('data:image/png;base64,RESULT'); }); // The detail the owner cared about most: judging fidelity without leaving // the picture. It has to be on the gallery too, not only on a fresh result. it('keeps the garment thumbnail on the render', () => { const { container } = renderGallery([item()]); const pip = container.querySelector('.aisthetix-tryon-figure-pip'); expect(pip?.getAttribute('src')).toBe('data:image/png;base64,GARMENT'); expect(pip?.getAttribute('alt')).toBe(en.gallery.garmentThumbnail); }); it('names an item saved without a product title', () => { renderGallery([item({ productTitle: undefined })]); expect(screen.getByText(en.gallery.untitledProduct)).toBeDefined(); }); it('writes an older try-on as a date', () => { renderGallery([item({ createdAt: new Date(2026, 5, 12, 9, 0, 0).toISOString() })]); expect(screen.getByText('Jun 12, 2026')).toBeDefined(); }); it('lists one row per try-on', () => { const { container } = renderGallery([ item(), item({ id: 'try-2', productTitle: 'Wool Coat' }), ]); expect(container.querySelectorAll('.aisthetix-gallery-item')).toHaveLength(2); }); }); // Nothing is for sale on the demo, so the row leads back to the item rather // than into a cart. describe('going back to the item', () => { it('links to the product page recorded when the try-on was made', () => { renderGallery([item({ productUrl: '/product/amalfi-linen-dress' })]); expect( screen.getByRole('link', { name: en.gallery.viewProduct }).getAttribute('href') ).toBe('/product/amalfi-linen-dress'); }); // A link that cannot go anywhere is worse than no link. it('omits the link on an item saved without a product url', () => { renderGallery([item({ productUrl: undefined })]); expect(screen.queryByRole('link', { name: en.gallery.viewProduct })).toBeNull(); expect(screen.getByText(en.gallery.share)).toBeDefined(); }); }); describe('sharing from the gallery', () => { it('hands the render to the share sheet as a file', async () => { const share = vi.fn().mockResolvedValue(undefined); Object.defineProperty(navigator, 'share', { value: share, configurable: true }); Object.defineProperty(navigator, 'canShare', { value: () => true, configurable: true }); renderGallery([item()]); fireEvent.click(screen.getByText(en.gallery.share)); await waitFor(() => expect(share).toHaveBeenCalledTimes(1)); expect(share.mock.calls[0][0].files[0].type).toBe('image/png'); }); it('saves the image and says so when there is no share sheet', async () => { renderGallery([item()]); fireEvent.click(screen.getByText(en.gallery.share)); await waitFor(() => expect(downloadImage).toHaveBeenCalledTimes(1)); expect(screen.getByText(en.share.downloaded)).toBeDefined(); }); }); describe('removing a try-on', () => { it('asks the parent to forget that id', () => { const { onDelete } = renderGallery([item({ id: 'try-42' })]); fireEvent.click(screen.getByLabelText(en.gallery.removeLabel('Linen Shirt'))); expect(onDelete).toHaveBeenCalledWith('try-42'); }); });