import React from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { act, render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import type { EventContextValue } from '../../../context/EventContext';
import type { TenantEvent } from '../../../api';
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string, fallback?: string) => fallback ?? key, i18n: { language: 'en-GB' } }),
}));
vi.mock('@tamagui/core', () => ({
useTheme: () => ({
background: { val: '#FFF8F5' },
surface: { val: '#ffffff' },
borderColor: { val: '#e5e7eb' },
color: { val: '#1f2937' },
gray: { val: '#6b7280' },
red10: { val: '#b91c1c' },
shadowColor: { val: 'rgba(0,0,0,0.12)' },
primary: { val: '#FF5A5F' },
}),
}));
vi.mock('@tamagui/stacks', () => ({
YStack: ({ children, ...props }: { children: React.ReactNode }) =>
{children}
,
XStack: ({ children, ...props }: { children: React.ReactNode }) => {children}
,
}));
vi.mock('@tamagui/text', () => ({
SizableText: ({ children, ...props }: { children: React.ReactNode }) => {children},
}));
vi.mock('@tamagui/react-native-web-lite', () => ({
Pressable: ({ children, onPress, ...props }: { children: React.ReactNode; onPress?: () => void }) => (
),
}));
vi.mock('tamagui', () => ({
Separator: ({ children }: { children?: React.ReactNode }) => {children}
,
Tabs: Object.assign(({ children }: { children: React.ReactNode }) => {children}
, {
List: ({ children }: { children: React.ReactNode }) => {children}
,
Tab: ({ children }: { children: React.ReactNode }) => {children}
,
Content: ({ children }: { children: React.ReactNode }) => {children}
,
}),
}));
vi.mock('../BottomNav', () => ({
BottomNav: () => ,
NavKey: {},
}));
vi.mock('../UserMenuSheet', () => ({
UserMenuSheet: () => ,
}));
const baseEvent: TenantEvent = {
id: 1,
name: 'Test Event',
slug: 'event-1',
event_date: '2024-01-01',
event_type_id: null,
event_type: null,
status: 'published',
settings: {},
};
const eventContext: EventContextValue = {
events: [],
isLoading: false,
isError: false,
activeEvent: baseEvent,
activeSlug: baseEvent.slug,
hasMultipleEvents: false,
hasEvents: true,
selectEvent: vi.fn(),
refetch: vi.fn(),
};
vi.mock('../../../context/EventContext', () => ({
useEventContext: () => eventContext,
}));
vi.mock('../../../auth/context', () => ({
useAuth: () => ({ user: { role: 'tenant_admin' } }),
}));
vi.mock('../../hooks/useMobileNav', () => ({
useMobileNav: () => ({ go: vi.fn(), slug: 'event-1' }),
}));
vi.mock('../../hooks/useNotificationsBadge', () => ({
useNotificationsBadge: () => ({ count: 0 }),
}));
vi.mock('../../hooks/useOnlineStatus', () => ({
useOnlineStatus: () => true,
}));
vi.mock('../../../api', () => ({
getEvents: vi.fn().mockResolvedValue([]),
}));
vi.mock('../../lib/tabHistory', () => ({
setTabHistory: vi.fn(),
}));
vi.mock('../../lib/photoModerationQueue', () => ({
loadPhotoQueue: vi.fn(() => []),
}));
vi.mock('../../lib/queueStatus', () => ({
countQueuedPhotoActions: vi.fn(() => 0),
}));
vi.mock('../../theme', () => ({
useAdminTheme: () => ({
background: '#FFF8F5',
surface: '#ffffff',
border: '#e5e7eb',
text: '#1f2937',
muted: '#6b7280',
warningBg: '#fff7ed',
warningText: '#92400e',
primary: '#FF5A5F',
danger: '#b91c1c',
shadow: 'rgba(0,0,0,0.12)',
glassSurface: 'rgba(255,255,255,0.8)',
glassSurfaceStrong: 'rgba(255,255,255,0.9)',
glassBorder: 'rgba(229,231,235,0.7)',
glassShadow: 'rgba(15,23,42,0.14)',
appBackground: 'linear-gradient(180deg, #f7fafc, #eef3f7)',
}),
}));
import { MobileShell } from '../MobileShell';
import { ADMIN_EVENTS_PATH } from '../../../constants';
describe('MobileShell', () => {
beforeEach(() => {
window.matchMedia = vi.fn().mockReturnValue({
matches: false,
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
});
document.title = '';
eventContext.events = [];
eventContext.hasMultipleEvents = false;
eventContext.activeEvent = { ...baseEvent };
eventContext.activeSlug = baseEvent.slug;
});
it('sets the document title with the app prefix', async () => {
await act(async () => {
render(
Body
);
});
expect(document.title).toBe('Fotospiel.App Event Admin ยท Dashboard');
});
it('renders quick QR as icon-only button', async () => {
await act(async () => {
render(
Body
);
});
expect(screen.getByLabelText('Quick QR')).toBeInTheDocument();
expect(screen.queryByText('Quick QR')).not.toBeInTheDocument();
});
it('hides the event context on compact headers', async () => {
window.matchMedia = vi.fn().mockReturnValue({
matches: true,
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
});
await act(async () => {
render(
Body
);
});
expect(screen.queryByText('Test Event')).not.toBeInTheDocument();
});
it('shows the event switcher when multiple events are available', async () => {
eventContext.events = [
{ ...baseEvent },
{
...baseEvent,
id: 2,
slug: 'event-2',
name: 'Second Event',
event_date: '2024-02-01',
},
];
await act(async () => {
render(
Body
);
});
expect(screen.getByLabelText('Switch event')).toBeInTheDocument();
});
it('hides the event switcher on the events list page', async () => {
eventContext.events = [
{ ...baseEvent },
{
...baseEvent,
id: 2,
slug: 'event-2',
name: 'Second Event',
event_date: '2024-02-01',
},
];
await act(async () => {
render(
Body
);
});
expect(screen.queryByLabelText('Switch event')).not.toBeInTheDocument();
});
});