import React from 'react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; const fetchGuestAiStylesMock = vi.fn(); const createGuestAiEditMock = vi.fn(); const fetchGuestAiEditStatusMock = vi.fn(); const translate = (key: string, options?: unknown, fallback?: string) => { if (typeof fallback === 'string') { return fallback; } if (typeof options === 'string') { return options; } return key; }; vi.mock('../services/aiEditsApi', () => ({ fetchGuestAiStyles: (...args: unknown[]) => fetchGuestAiStylesMock(...args), createGuestAiEdit: (...args: unknown[]) => createGuestAiEditMock(...args), fetchGuestAiEditStatus: (...args: unknown[]) => fetchGuestAiEditStatusMock(...args), })); vi.mock('@/shared/guest/i18n/useTranslation', () => ({ useTranslation: () => ({ t: translate, }), })); vi.mock('../lib/toast', () => ({ pushGuestToast: vi.fn(), })); vi.mock('../lib/guestTheme', () => ({ useGuestThemeVariant: () => ({ isDark: false }), })); vi.mock('lucide-react', () => ({ Copy: () => copy, Download: () => download, Loader2: () => loader, MessageSquare: () => message, RefreshCcw: () => refresh, Share2: () => share, Sparkles: () => sparkles, Wand2: () => wand, X: () => x, })); vi.mock('@tamagui/sheet', () => { const Sheet = ({ open, children }: { open?: boolean; children: React.ReactNode }) => (open ?
{children}
: null); Sheet.Overlay = ({ children }: { children?: React.ReactNode }) =>
{children}
; Sheet.Frame = ({ children }: { children?: React.ReactNode }) =>
{children}
; Sheet.Handle = ({ children }: { children?: React.ReactNode }) =>
{children}
; return { Sheet }; }); vi.mock('@tamagui/stacks', () => ({ YStack: ({ children }: { children: React.ReactNode }) =>
{children}
, XStack: ({ children }: { children: React.ReactNode }) =>
{children}
, })); vi.mock('@tamagui/text', () => ({ SizableText: ({ children }: { children: React.ReactNode }) => {children}, })); vi.mock('@tamagui/button', () => ({ Button: ({ children, onPress, ...rest }: { children: React.ReactNode; onPress?: () => void }) => ( ), })); import AiMagicEditSheet from '../components/AiMagicEditSheet'; describe('AiMagicEditSheet', () => { const originalOnLine = navigator.onLine; beforeEach(() => { fetchGuestAiStylesMock.mockReset(); createGuestAiEditMock.mockReset(); fetchGuestAiEditStatusMock.mockReset(); Object.defineProperty(window.navigator, 'onLine', { configurable: true, value: true, }); }); afterEach(() => { vi.useRealTimers(); Object.defineProperty(window.navigator, 'onLine', { configurable: true, value: originalOnLine, }); }); it('loads styles and creates an ai edit request', async () => { fetchGuestAiStylesMock.mockResolvedValue({ data: [ { id: 1, key: 'ghibli-soft', name: 'Ghibli Soft', description: 'Soft shading style', }, ], meta: {}, }); createGuestAiEditMock.mockResolvedValue({ duplicate: false, data: { id: 15, event_id: 2, photo_id: 7, status: 'succeeded', style: { id: 1, key: 'ghibli-soft', name: 'Ghibli Soft' }, outputs: [{ id: 99, provider_url: 'https://example.com/ai.jpg', is_primary: true }], }, }); render( ); expect(await screen.findByText('Ghibli Soft')).toBeInTheDocument(); fireEvent.click(screen.getByText('Generate AI edit')); await waitFor(() => expect(createGuestAiEditMock).toHaveBeenCalledTimes(1)); await waitFor(() => expect(screen.getByText('AI result')).toBeInTheDocument()); expect(screen.getByText('Copy link')).toBeInTheDocument(); }); it('shows an error when style loading fails', async () => { fetchGuestAiStylesMock.mockRejectedValue(new Error('Styles not reachable')); render( ); expect(await screen.findByText('Styles not reachable')).toBeInTheDocument(); }); it('pauses polling while offline and resumes after reconnect', async () => { Object.defineProperty(window.navigator, 'onLine', { configurable: true, value: false, }); fetchGuestAiStylesMock.mockResolvedValue({ data: [{ id: 1, key: 'ghibli-soft', name: 'Ghibli Soft' }], meta: {}, }); createGuestAiEditMock.mockResolvedValue({ duplicate: false, data: { id: 22, event_id: 2, photo_id: 7, status: 'processing', style: { id: 1, key: 'ghibli-soft', name: 'Ghibli Soft' }, outputs: [], }, }); fetchGuestAiEditStatusMock.mockResolvedValue({ data: { id: 22, event_id: 2, photo_id: 7, status: 'succeeded', style: { id: 1, key: 'ghibli-soft', name: 'Ghibli Soft' }, outputs: [{ id: 7, provider_url: 'https://example.com/generated.jpg', is_primary: true }], }, }); render( ); expect(await screen.findByText('Ghibli Soft')).toBeInTheDocument(); fireEvent.click(screen.getByText('Generate AI edit')); await waitFor(() => expect(createGuestAiEditMock).toHaveBeenCalledTimes(1)); expect(await screen.findByText('You are offline. Status updates resume automatically when connection is back.')).toBeInTheDocument(); vi.useFakeTimers(); await vi.advanceTimersByTimeAsync(7000); expect(fetchGuestAiEditStatusMock).toHaveBeenCalledTimes(0); Object.defineProperty(window.navigator, 'onLine', { configurable: true, value: true, }); await act(async () => { window.dispatchEvent(new Event('online')); }); await act(async () => { await vi.advanceTimersByTimeAsync(3000); }); await Promise.resolve(); expect(fetchGuestAiEditStatusMock).toHaveBeenCalledTimes(1); }); });