import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { getTranslations } from '../i18n'; import type { WidgetSizeConfig } from '../types'; import { SizeFlow } from './SizeFlow'; vi.mock('../imageUtils', () => ({ fileToBase64: vi.fn().mockResolvedValue('data:image/jpeg;base64,WA=='), resizeAvatarImage: vi.fn().mockResolvedValue('data:image/jpeg;base64,WA=='), })); const PHOTO = 'data:image/jpeg;base64,WA=='; const VARIANTS = [{ variant_id: '12', size_label: 'M', available: true }]; const CONFIG: WidgetSizeConfig = { hasSizeGuide: true, guides: [{ id: 'g1', categories: { categories: ['shirts'] }, labels: ['M'] }], targeting: { mode: 'all' as const, categories: [], productIds: [] }, }; function renderFlow(api: any, sizeConfig = CONFIG) { return render( ); } describe('WooCommerce size flow', () => { beforeEach(() => localStorage.clear()); it('shows the photo refusal before collecting height or weight', async () => { const api = { probePhotoQuality: vi.fn().mockResolvedValue({ ok: false, reason: 'not_full_body' }), estimate: vi.fn(), }; renderFlow(api); await screen.findByText(/head to feet/i); expect(screen.queryByLabelText(/height/i)).toBeNull(); expect(api.estimate).not.toHaveBeenCalled(); }); it('returns a variant-backed recommendation', async () => { const api = { probePhotoQuality: vi.fn().mockResolvedValue({ ok: true, reason: null }), estimate: vi.fn().mockResolvedValue({ recommended_size: 'M', recommended_variant_id: '12', confidence: 0.8, confidence_band: 'good', explanation: '', alternatives: [], }), }; renderFlow(api); await waitFor(() => expect(api.probePhotoQuality).toHaveBeenCalled()); fireEvent.click(screen.getByRole('button', { name: /find my size/i })); fireEvent.change(screen.getByLabelText(/height/i), { target: { value: '175' } }); fireEvent.click(screen.getByRole('button', { name: /get my size/i })); await screen.findByText('M'); expect(api.estimate).toHaveBeenCalledWith( expect.objectContaining({ productId: '7', heightCm: 175, productVariants: VARIANTS }), expect.any(AbortSignal) ); }); it('sends the shopper fit preference only when the merchant enabled the choice', async () => { const api = { probePhotoQuality: vi.fn().mockResolvedValue({ ok: true, reason: null }), estimate: vi.fn().mockResolvedValue({ recommended_size: 'M', recommended_variant_id: '12', confidence: 0.8, confidence_band: 'good', explanation: '', alternatives: [], }), }; renderFlow(api, { ...CONFIG, fitPreferenceEnabled: true, defaultFitPreference: 'regular' as const }); await waitFor(() => expect(api.probePhotoQuality).toHaveBeenCalled()); fireEvent.click(screen.getByRole('button', { name: /find my size/i })); fireEvent.change(screen.getByLabelText(/height/i), { target: { value: '175' } }); fireEvent.change(screen.getByLabelText(/prefer it to fit/i), { target: { value: 'loose' } }); fireEvent.click(screen.getByRole('button', { name: /get my size/i })); await waitFor(() => expect(api.estimate).toHaveBeenCalledWith( expect.objectContaining({ fitPreference: 'loose' }), expect.any(AbortSignal) )); }); it('can re-estimate with a replacement measurement photo', async () => { const answer = { recommended_size: 'M', recommended_variant_id: '12', confidence: 0.5, confidence_band: 'low', explanation: '', alternatives: [], improvement_options: ['retake_photo'], }; const api = { probePhotoQuality: vi.fn().mockResolvedValue({ ok: true, reason: null }), estimate: vi.fn().mockResolvedValue(answer), }; renderFlow(api); await waitFor(() => expect(api.probePhotoQuality).toHaveBeenCalled()); fireEvent.click(screen.getByRole('button', { name: /find my size/i })); fireEvent.change(screen.getByLabelText(/height/i), { target: { value: '175' } }); fireEvent.click(screen.getByRole('button', { name: /get my size/i })); const input = await screen.findByLabelText(/better full-body photo/i); fireEvent.change(input, { target: { files: [new File(['photo'], 'body.jpg', { type: 'image/jpeg' })] } }); await waitFor(() => expect(api.estimate).toHaveBeenCalledTimes(2)); }); });