diff --git a/resources/js/guest-v2/__tests__/AppShell.test.tsx b/resources/js/guest-v2/__tests__/AppShell.test.tsx new file mode 100644 index 00000000..fdbebdb5 --- /dev/null +++ b/resources/js/guest-v2/__tests__/AppShell.test.tsx @@ -0,0 +1,117 @@ +import React from 'react'; +import { describe, expect, it, vi } from 'vitest'; +import { render, screen } from '@testing-library/react'; + +const useEventDataMock = vi.fn(); + +vi.mock('react-router-dom', () => ({ + useNavigate: () => vi.fn(), + useLocation: () => ({ pathname: '/e/demo' }), +})); + +vi.mock('../context/EventDataContext', () => ({ + useEventData: () => useEventDataMock(), +})); + +vi.mock('@/shared/guest/context/NotificationCenterContext', () => ({ + useOptionalNotificationCenter: () => ({ unreadCount: 0 }), +})); + +vi.mock('@/shared/guest/i18n/useTranslation', () => ({ + useTranslation: () => ({ + t: (_key: string, fallback?: string) => fallback ?? _key, + }), +})); + +vi.mock('../lib/guestTheme', () => ({ + useGuestThemeVariant: () => ({ isDark: false }), +})); + +vi.mock('@tamagui/stacks', () => ({ + XStack: ({ children }: { children: React.ReactNode }) =>
{children}
, + YStack: ({ children }: { children: React.ReactNode }) =>
{children}
, +})); + +vi.mock('@tamagui/button', () => ({ + Button: ({ children, disabled, ...rest }: { children: React.ReactNode; disabled?: boolean }) => ( + + ), +})); + +vi.mock('../components/TopBar', () => ({ + default: () =>
topbar
, +})); + +vi.mock('../components/FloatingActionButton', () => ({ + default: () =>
fab
, +})); + +vi.mock('../components/CompassHub', () => ({ + default: ({ quadrants }: { quadrants: Array<{ key: string; label: string; disabled?: boolean }> }) => ( +
+ {quadrants.map((item) => ( + + ))} +
+ ), +})); + +vi.mock('../components/AmbientBackground', () => ({ + default: ({ children }: { children: React.ReactNode }) =>
{children}
, +})); + +vi.mock('../components/NotificationSheet', () => ({ + default: () => null, +})); + +vi.mock('../components/SettingsSheet', () => ({ + default: () => null, +})); + +vi.mock('../components/GuestAnalyticsNudge', () => ({ + default: () => null, +})); + +vi.mock('lucide-react', () => ({ + Sparkles: () => sparkles, + Share2: () => share, + Image: () => image, + Camera: () => camera, + Settings: () => settings, + Home: () => home, + Menu: () => menu, +})); + +import AppShell from '../components/AppShell'; + +describe('AppShell', () => { + it('disables task link when no active tasks are available', () => { + useEventDataMock.mockReturnValue({ + tasksEnabled: true, + hasActiveTasks: false, + event: { name: 'Demo Event' }, + token: 'demo', + }); + + render(
content
); + + expect(screen.getByRole('button', { name: 'Tasks' })).toBeDisabled(); + }); + + it('keeps task link enabled when active tasks exist', () => { + useEventDataMock.mockReturnValue({ + tasksEnabled: true, + hasActiveTasks: true, + event: { name: 'Demo Event' }, + token: 'demo', + }); + + render(
content
); + + expect(screen.getByRole('button', { name: 'Tasks' })).toBeEnabled(); + }); +}); diff --git a/resources/js/guest-v2/__tests__/HomeScreen.test.tsx b/resources/js/guest-v2/__tests__/HomeScreen.test.tsx index b567d995..543c89b2 100644 --- a/resources/js/guest-v2/__tests__/HomeScreen.test.tsx +++ b/resources/js/guest-v2/__tests__/HomeScreen.test.tsx @@ -135,6 +135,7 @@ describe('HomeScreen', () => { fetchGalleryMock.mockResolvedValueOnce({ data: [] }); useEventDataMock.mockReturnValue({ tasksEnabled: true, + hasActiveTasks: true, token: 'demo', event: { name: 'Demo Event' }, }); @@ -149,6 +150,7 @@ describe('HomeScreen', () => { fetchGalleryMock.mockResolvedValueOnce({ data: [] }); useEventDataMock.mockReturnValue({ tasksEnabled: false, + hasActiveTasks: false, token: 'demo', event: { name: 'Demo Event' }, }); @@ -165,6 +167,7 @@ describe('HomeScreen', () => { }); useEventDataMock.mockReturnValue({ tasksEnabled: false, + hasActiveTasks: false, token: 'demo', event: { name: 'Demo Event' }, }); @@ -176,4 +179,34 @@ describe('HomeScreen', () => { expect(navigateMock).toHaveBeenCalledWith('/e/demo/gallery?photo=42'); }); + + it('does not mention tasks when no active tasks are available', () => { + fetchGalleryMock.mockResolvedValueOnce({ data: [] }); + useEventDataMock.mockReturnValue({ + tasksEnabled: true, + hasActiveTasks: false, + token: 'demo', + event: { name: 'Demo Event' }, + }); + + render(); + + expect(screen.getByText('Capture ready')).toBeInTheDocument(); + expect(screen.queryByText("Let's go!")).not.toBeInTheDocument(); + }); + + it('shows a friendly empty gallery message instead of placeholders', async () => { + fetchGalleryMock.mockResolvedValueOnce({ data: [] }); + useEventDataMock.mockReturnValue({ + tasksEnabled: false, + hasActiveTasks: false, + token: 'demo', + event: { name: 'Demo Event' }, + }); + + render(); + + expect(await screen.findByText('No photos yet')).toBeInTheDocument(); + expect(screen.getByText('You should start taking some and fill this gallery with moments.')).toBeInTheDocument(); + }); }); diff --git a/resources/js/guest-v2/components/AppShell.tsx b/resources/js/guest-v2/components/AppShell.tsx index 62625712..4a798a78 100644 --- a/resources/js/guest-v2/components/AppShell.tsx +++ b/resources/js/guest-v2/components/AppShell.tsx @@ -24,7 +24,7 @@ export default function AppShell({ children }: AppShellProps) { const [compassOpen, setCompassOpen] = React.useState(false); const [notificationsOpen, setNotificationsOpen] = React.useState(false); const [settingsOpen, setSettingsOpen] = React.useState(false); - const { tasksEnabled, event, token } = useEventData(); + const { tasksEnabled, hasActiveTasks, event, token } = useEventData(); const notificationCenter = useOptionalNotificationCenter(); const navigate = useNavigate(); const location = useLocation(); @@ -73,7 +73,8 @@ export default function AppShell({ children }: AppShellProps) { key: 'tasks', label: t('navigation.tasks', 'Tasks'), icon: , - onPress: goTo('/tasks'), + onPress: hasActiveTasks ? goTo('/tasks') : undefined, + disabled: !hasActiveTasks, } : { key: 'settings', diff --git a/resources/js/guest-v2/components/CompassHub.tsx b/resources/js/guest-v2/components/CompassHub.tsx index f8e9c47e..29fc416c 100644 --- a/resources/js/guest-v2/components/CompassHub.tsx +++ b/resources/js/guest-v2/components/CompassHub.tsx @@ -11,6 +11,7 @@ export type CompassAction = { label: string; icon?: React.ReactNode; onPress?: () => void; + disabled?: boolean; }; type CompassHubProps = { @@ -128,7 +129,12 @@ export default function CompassHub({ {quadrants.map((action, index) => ( + + ) : null}