// @vitest-environment jsdom import { afterEach, beforeEach, it, describe, expect, vi } from 'vitest'; import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'; import { App } from './App'; import { en } from './i18n/en'; import { it as italian } from './i18n/it'; import { TryOnRejectedError } from './tryOnErrors'; import { setConsent, getAvatar } from './storage'; const {tryOnSync} = vi.hoisted(() => ({tryOnSync: vi.fn()})); vi.mock('./api', async original => ({...await original(), VirtualTryOnAPI: vi.fn(() => ({tryOnSync}))})); vi.mock('./imageUtils', async original => ({...await original(), fetchImageAsBase64: vi.fn().mockResolvedValue('data:image/png;base64,WA=='), compressGarmentImage: vi.fn().mockResolvedValue('WA==')})); const saved = 'data:image/png;base64,WA=='; function mount(locale = 'en') { const trigger = document.createElement('button'); trigger.id = 'aisthetix-tryon-trigger'; document.body.append(trigger); render(); fireEvent.click(trigger); return trigger; } beforeEach(() => { localStorage.clear(); tryOnSync.mockReset(); localStorage.setItem('aisthetix-avatar', JSON.stringify({imageData: saved, createdAt: new Date().toISOString()})); }); afterEach(() => {cleanup(); document.body.innerHTML = '';}); it.each([false, true])('reopens a cached photo without any generation, consent=%s', async consent => { if (consent) setConsent(); const trigger = mount(); expect(screen.getByText(en.upload.reuseTitle)).toBeDefined(); expect(screen.getByRole('button', {name: en.upload.useThis}).hasAttribute('disabled')).toBe(!consent); fireEvent.click(screen.getByRole('button', {name: 'Close'})); await waitFor(() => expect(screen.queryByRole('dialog')).toBeNull()); fireEvent.click(trigger); expect(screen.getByText(en.upload.reuseTitle)).toBeDefined(); expect(tryOnSync).not.toHaveBeenCalled(); if (!consent) fireEvent.click(screen.getByRole('checkbox')); tryOnSync.mockReturnValue(new Promise(() => {})); fireEvent.click(screen.getByRole('button', {name: en.upload.useThis})); await waitFor(() => expect(tryOnSync).toHaveBeenCalledOnce()); expect(document.querySelector('.aisthetix-processing-avatar')?.getAttribute('src')).toBe(saved); }); describe.each([['en', en], ['it', italian]] as const)('recovery (%s)', (locale, t) => { it.each(['photo_body_hidden','content_refused','generation_refused','quality_failed','bad_image'] as const)('handles %s and preserves saved photo', async code => { setConsent(); tryOnSync.mockRejectedValue(new TryOnRejectedError(code)); mount(locale); fireEvent.click(screen.getByRole('button', {name: t.upload.useThis})); const message = code === 'photo_body_hidden' ? t.errors.photoBodyHidden : code === 'quality_failed' ? t.errors.qualityFailed : code === 'bad_image' ? t.upload.unreadableImage : t.errors.refused; expect(await screen.findByText(message)).toBeDefined(); expect(Boolean(screen.queryByRole('button', {name: t.error.tryAgain}))).toBe(code === 'quality_failed'); fireEvent.click(screen.getByRole('button', {name: t.error.useAnotherPhoto})); expect(document.querySelector('.aisthetix-preview-image')).toBeNull(); expect(screen.getByText(t.upload.guide.tip)).toBeDefined(); expect(getAvatar()?.imageData).toBe(saved); expect(tryOnSync).toHaveBeenCalledOnce(); }); }); it.each([['en', en], ['it', italian]] as const)('passes the confirmed shopper photo, garment and result into comparison (%s)', async (locale, t) => { setConsent(); tryOnSync.mockResolvedValue({image: 'RESULT', cached: false}); mount(locale); fireEvent.click(screen.getByRole('button', {name: t.upload.useThis})); expect(await screen.findByRole('slider', {name: t.result.beforeAfterSlider})).toHaveProperty('value', '50'); expect(screen.getByRole('img', {name: t.result.beforePhoto}).getAttribute('src')).toBe(saved); expect(screen.getByRole('img', {name: t.result.afterTryOn}).getAttribute('src')).toBe('data:image/png;base64,RESULT'); fireEvent.click(screen.getByRole('button', {name: t.result.toggleCompare})); expect(screen.getByRole('img', {name: t.result.compareOriginal}).getAttribute('src')).toBe('WA=='); expect(screen.getByRole('img', {name: t.result.compareTryOn}).getAttribute('src')).toBe('data:image/png;base64,RESULT'); expect(tryOnSync).toHaveBeenCalledOnce(); });