/** @vitest-environment jsdom */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, cleanup, fireEvent, waitFor } from '@testing-library/react'; import { DemoResultView } from './DemoResultView'; import { downloadImage } from '../imageUtils'; import { en } from '../i18n/en'; // The only import of imageUtils in this tree is the share fallback. vi.mock('../imageUtils', () => ({ downloadImage: vi.fn() })); const RESULT = 'data:image/png;base64,RESULT'; const GARMENT = 'data:image/png;base64,GARMENT'; type Props = React.ComponentProps; function renderResult(overrides: Partial = {}) { const props: Props = { resultImage: RESULT, originalClothingImage: GARMENT, productTitle: 'Amalfi Linen Dress', productUrl: '/product/amalfi-linen-dress', onTryAnother: vi.fn(), onClose: vi.fn(), t: en.result, shareT: en.share, ...overrides, }; return { props, ...render() }; } beforeEach(() => { vi.mocked(downloadImage).mockClear(); }); afterEach(() => { cleanup(); Reflect.deleteProperty(navigator, 'share'); Reflect.deleteProperty(navigator, 'canShare'); }); // The detail the owner cared about most: judging fidelity without leaving the // picture. It is the whole reason this view exists instead of the merchant one. describe('the garment on the render', () => { it('sits on the result as a thumbnail', () => { const { container } = renderResult(); const pip = container.querySelector('.aisthetix-tryon-figure-pip'); expect(pip?.getAttribute('src')).toBe(GARMENT); expect(pip?.getAttribute('alt')).toBe(en.result.garmentThumbnail); }); it('is simply absent when the garment image never arrived', () => { const { container } = renderResult({ originalClothingImage: undefined }); expect(container.querySelector('.aisthetix-tryon-figure-image')?.getAttribute('src')).toBe( RESULT ); expect(container.querySelector('.aisthetix-tryon-figure-pip')).toBeNull(); }); }); describe('comparing against the original', () => { it('swaps to the side by side view and back', () => { const { container } = renderResult(); fireEvent.click(screen.getByText(en.result.toggleCompare)); expect(container.querySelectorAll('.aisthetix-compare-image')).toHaveLength(2); fireEvent.click(screen.getByText(en.result.toggleResult)); expect(container.querySelector('.aisthetix-tryon-figure-pip')).toBeTruthy(); }); }); describe('sharing the result', () => { it('hands the render to the share sheet as a file, with no upload', async () => { const share = vi.fn().mockResolvedValue(undefined); Object.defineProperty(navigator, 'share', { value: share, configurable: true }); Object.defineProperty(navigator, 'canShare', { value: () => true, configurable: true }); renderResult(); fireEvent.click(screen.getByText(en.result.share)); await waitFor(() => expect(share).toHaveBeenCalledTimes(1)); const payload = share.mock.calls[0][0]; expect(payload.files[0].type).toBe('image/png'); expect(payload.files[0].name).toContain('amalfi-linen-dress'); expect(downloadImage).not.toHaveBeenCalled(); }); // No share sheet means the file is saved instead, which is silent enough that // the shopper needs to be told it happened. it('saves the image and says so when there is no share sheet', async () => { renderResult(); fireEvent.click(screen.getByText(en.result.share)); await waitFor(() => expect(downloadImage).toHaveBeenCalledTimes(1)); expect(screen.getByRole('status').textContent).toBe(en.share.downloaded); }); }); describe('the way onward', () => { it('offers a different photo and a way back to the store', () => { const { props } = renderResult(); fireEvent.click(screen.getByText(en.result.tryDifferent)); expect(props.onTryAnother).toHaveBeenCalledTimes(1); fireEvent.click(screen.getByText(en.result.continueShopping)); expect(props.onClose).toHaveBeenCalledTimes(1); }); it('names what was tried on', () => { renderResult(); expect(screen.getByText(en.result.tryingOn('Amalfi Linen Dress'))).toBeTruthy(); }); });