import React from 'react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { render, waitFor } from '@testing-library/react'; const setSearchParamsMock = vi.fn(); const pushGuestToastMock = vi.fn(); vi.mock('react-router-dom', () => ({ useNavigate: () => vi.fn(), useSearchParams: () => [new URLSearchParams('photo=123'), setSearchParamsMock], })); vi.mock('../context/EventDataContext', () => ({ useEventData: () => ({ token: 'demo', event: { name: 'Demo Event' } }), })); vi.mock('../hooks/usePollGalleryDelta', () => ({ usePollGalleryDelta: () => ({ data: { photos: [] } }), })); vi.mock('../hooks/usePollStats', () => ({ usePollStats: () => ({ stats: { onlineGuests: 0, guestCount: 0, likesCount: 0 } }), })); vi.mock('@/guest/i18n/useTranslation', () => ({ useTranslation: () => ({ t: (key: string, options?: unknown, fallback?: string) => { if (typeof fallback === 'string') return fallback; if (typeof options === 'string') return options; return key; }, }), })); vi.mock('@/guest/i18n/LocaleContext', () => ({ useLocale: () => ({ locale: 'de' }), })); vi.mock('../lib/guestTheme', () => ({ useGuestThemeVariant: () => ({ isDark: false }), })); vi.mock('../lib/bento', () => ({ getBentoSurfaceTokens: () => ({ backgroundColor: '#fff', borderColor: '#eee', borderBottomColor: '#ddd', shadow: 'none', }), })); const fetchGalleryMock = vi.fn().mockResolvedValue({ data: [] }); const fetchPhotoMock = vi.fn().mockRejectedValue(Object.assign(new Error('not found'), { status: 404 })); vi.mock('../services/photosApi', () => ({ fetchGallery: (...args: unknown[]) => fetchGalleryMock(...args), fetchPhoto: (...args: unknown[]) => fetchPhotoMock(...args), likePhoto: vi.fn(), unlikePhoto: vi.fn(), createPhotoShareLink: vi.fn(), deletePhoto: vi.fn(), })); vi.mock('../components/AppShell', () => ({ default: ({ children }: { children: React.ReactNode }) =>
{children}
, })); vi.mock('../components/PhotoFrameTile', () => ({ default: ({ children }: { children: React.ReactNode }) =>
{children}
, })); vi.mock('../components/ShareSheet', () => ({ default: () => null, })); vi.mock('../lib/toast', () => ({ pushGuestToast: (...args: unknown[]) => pushGuestToastMock(...args), })); 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 }) => ( ), })); vi.mock('lucide-react', () => ({ Camera: () => camera, Sparkles: () => sparkles, Heart: () => heart, Share2: () => share, ChevronLeft: () => left, ChevronRight: () => right, Loader2: () => loader, Download: () => download, X: () => x, Trash2: () => trash, })); import GalleryScreen from '../screens/GalleryScreen'; describe('GalleryScreen', () => { beforeEach(() => { setSearchParamsMock.mockClear(); pushGuestToastMock.mockClear(); fetchGalleryMock.mockReset(); fetchPhotoMock.mockReset(); }); afterEach(() => { vi.useRealTimers(); }); it('clears the photo param and shows a warning when lightbox fails to load', async () => { vi.useFakeTimers(); fetchGalleryMock.mockResolvedValue({ data: [] }); fetchPhotoMock.mockRejectedValue(Object.assign(new Error('not found'), { status: 404 })); render(); for (let i = 0; i < 7; i += 1) { await vi.advanceTimersByTimeAsync(1500); await Promise.resolve(); } await vi.advanceTimersByTimeAsync(1000); await vi.runAllTimersAsync(); await Promise.resolve(); expect(pushGuestToastMock).toHaveBeenCalled(); expect(setSearchParamsMock).toHaveBeenCalled(); const [params] = setSearchParamsMock.mock.calls.at(-1) ?? []; const search = params instanceof URLSearchParams ? params : new URLSearchParams(params); expect(search.get('photo')).toBeNull(); }); it('keeps lightbox open when a seeded photo exists but fetch fails', async () => { vi.useFakeTimers(); fetchGalleryMock.mockResolvedValue({ data: [{ id: 123, thumbnail_url: '/storage/demo.jpg', likes_count: 2 }], }); fetchPhotoMock.mockRejectedValue(Object.assign(new Error('not found'), { status: 404 })); render(); await Promise.resolve(); await vi.runAllTimersAsync(); expect(fetchGalleryMock).toHaveBeenCalled(); expect(fetchPhotoMock).toHaveBeenCalled(); await vi.advanceTimersByTimeAsync(1000); expect(setSearchParamsMock).not.toHaveBeenCalled(); expect(pushGuestToastMock).not.toHaveBeenCalled(); }); });