upgrade to tamagui v2 and guest pwa overhaul
This commit is contained in:
53
resources/js/guest-v2/__tests__/BottomDock.test.tsx
Normal file
53
resources/js/guest-v2/__tests__/BottomDock.test.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
vi.mock('react-router-dom', () => ({
|
||||
useLocation: () => ({ pathname: '/e/demo' }),
|
||||
useNavigate: () => vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../context/EventDataContext', () => ({
|
||||
useEventData: () => ({ token: 'demo' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (_key: string, fallback?: string) => fallback ?? _key,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/stacks', () => ({
|
||||
XStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
YStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/text', () => ({
|
||||
SizableText: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/button', () => ({
|
||||
Button: ({ children, ...rest }: { children: React.ReactNode }) => (
|
||||
<button type="button" {...rest}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('lucide-react', () => ({
|
||||
Home: () => <span>home</span>,
|
||||
Image: () => <span>image</span>,
|
||||
Share2: () => <span>share</span>,
|
||||
}));
|
||||
|
||||
import BottomDock from '../components/BottomDock';
|
||||
|
||||
describe('BottomDock', () => {
|
||||
it('renders navigation labels', () => {
|
||||
render(<BottomDock />);
|
||||
|
||||
expect(screen.getByText('Home')).toBeInTheDocument();
|
||||
expect(screen.getByText('Gallery')).toBeInTheDocument();
|
||||
expect(screen.getByText('Share')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
53
resources/js/guest-v2/__tests__/EventLayout.test.tsx
Normal file
53
resources/js/guest-v2/__tests__/EventLayout.test.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
let identityState = { hydrated: true, name: '' };
|
||||
|
||||
vi.mock('react-router-dom', () => ({
|
||||
useParams: () => ({ token: 'demo-token' }),
|
||||
Navigate: ({ to }: { to: string }) => <div>navigate:{to}</div>,
|
||||
Outlet: () => <div>outlet</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../context/EventDataContext', () => ({
|
||||
EventDataProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
useEventData: () => ({ event: null }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/context/EventBrandingContext', () => ({
|
||||
EventBrandingProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/LocaleContext', () => ({
|
||||
LocaleProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
DEFAULT_LOCALE: 'de',
|
||||
isLocaleCode: () => true,
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/context/NotificationCenterContext', () => ({
|
||||
NotificationCenterProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
}));
|
||||
|
||||
vi.mock('../context/GuestIdentityContext', () => ({
|
||||
GuestIdentityProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
useOptionalGuestIdentity: () => identityState,
|
||||
}));
|
||||
|
||||
import EventLayout from '../layouts/EventLayout';
|
||||
|
||||
describe('EventLayout profile gate', () => {
|
||||
it('redirects to setup when profile is missing', () => {
|
||||
identityState = { hydrated: true, name: '' };
|
||||
render(<EventLayout requireProfile />);
|
||||
|
||||
expect(screen.getByText('navigate:/setup/demo-token')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders outlet when profile exists', () => {
|
||||
identityState = { hydrated: true, name: 'Ava' };
|
||||
render(<EventLayout requireProfile />);
|
||||
|
||||
expect(screen.getByText('outlet')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
73
resources/js/guest-v2/__tests__/HelpCenterScreen.test.tsx
Normal file
73
resources/js/guest-v2/__tests__/HelpCenterScreen.test.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
vi.mock('@tamagui/stacks', () => ({
|
||||
YStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
XStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/text', () => ({
|
||||
SizableText: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/button', () => ({
|
||||
Button: ({ children, ...rest }: { children: React.ReactNode }) => (
|
||||
<button type="button" {...rest}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/input', () => ({
|
||||
Input: ({ value }: { value?: string }) => <input value={value} readOnly />,
|
||||
}));
|
||||
|
||||
vi.mock('../components/AppShell', () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../components/StandaloneShell', () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../components/SurfaceCard', () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/components/PullToRefresh', () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({ t: (_key: string, fallback?: string) => fallback ?? _key, locale: 'de' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/LocaleContext', () => ({
|
||||
useLocale: () => ({ locale: 'de' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/services/helpApi', () => ({
|
||||
getHelpArticles: () => Promise.resolve({
|
||||
servedFromCache: false,
|
||||
articles: [{ slug: 'intro', title: 'Intro', summary: 'Summary', updated_at: null }],
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@/hooks/use-appearance', () => ({
|
||||
useAppearance: () => ({ resolved: 'light' }),
|
||||
}));
|
||||
|
||||
vi.mock('react-router-dom', () => ({
|
||||
useParams: () => ({}),
|
||||
Link: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
import HelpCenterScreen from '../screens/HelpCenterScreen';
|
||||
|
||||
describe('HelpCenterScreen', () => {
|
||||
it('renders help center title', async () => {
|
||||
render(<HelpCenterScreen />);
|
||||
expect(await screen.findByText('help.center.title')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
75
resources/js/guest-v2/__tests__/HomeScreen.test.tsx
Normal file
75
resources/js/guest-v2/__tests__/HomeScreen.test.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { EventDataProvider } from '../context/EventDataContext';
|
||||
|
||||
vi.mock('react-router-dom', () => ({
|
||||
useNavigate: () => vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/stacks', () => ({
|
||||
YStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
XStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/text', () => ({
|
||||
SizableText: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/button', () => ({
|
||||
Button: ({ children, ...rest }: { children: React.ReactNode }) => (
|
||||
<button type="button" {...rest}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('lucide-react', () => ({
|
||||
Camera: () => <span>camera</span>,
|
||||
Sparkles: () => <span>sparkles</span>,
|
||||
Image: () => <span>image</span>,
|
||||
Star: () => <span>star</span>,
|
||||
Trophy: () => <span>trophy</span>,
|
||||
}));
|
||||
|
||||
vi.mock('../components/AppShell', () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../services/uploadApi', () => ({
|
||||
useUploadQueue: () => ({ items: [], loading: false, refresh: vi.fn() }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({ t: (key: string, fallback?: string) => fallback ?? key, locale: 'de' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/hooks/use-appearance', () => ({
|
||||
useAppearance: () => ({ resolved: 'light' }),
|
||||
}));
|
||||
|
||||
import HomeScreen from '../screens/HomeScreen';
|
||||
|
||||
describe('HomeScreen', () => {
|
||||
it('shows prompt quest content when tasks are enabled', () => {
|
||||
render(
|
||||
<EventDataProvider tasksEnabledFallback>
|
||||
<HomeScreen />
|
||||
</EventDataProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Prompt quest')).toBeInTheDocument();
|
||||
expect(screen.getByText('Start prompt')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows capture-ready content when tasks are disabled', () => {
|
||||
render(
|
||||
<EventDataProvider tasksEnabledFallback={false}>
|
||||
<HomeScreen />
|
||||
</EventDataProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Capture ready')).toBeInTheDocument();
|
||||
expect(screen.getByText('Upload / Take photo')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
61
resources/js/guest-v2/__tests__/LandingScreen.test.tsx
Normal file
61
resources/js/guest-v2/__tests__/LandingScreen.test.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { LocaleProvider } from '@/guest/i18n/LocaleContext';
|
||||
|
||||
vi.mock('react-router-dom', () => ({
|
||||
useNavigate: () => vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/stacks', () => ({
|
||||
YStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
XStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/text', () => ({
|
||||
SizableText: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/button', () => ({
|
||||
Button: ({ children, ...rest }: { children: React.ReactNode }) => (
|
||||
<button type="button" {...rest}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/input', () => ({
|
||||
Input: ({ ...rest }: { [key: string]: unknown }) => <input {...rest} />,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/card', () => ({
|
||||
Card: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('html5-qrcode', () => ({
|
||||
Html5Qrcode: vi.fn().mockImplementation(() => ({
|
||||
start: vi.fn(),
|
||||
stop: vi.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock('lucide-react', () => ({
|
||||
QrCode: () => <span>qr</span>,
|
||||
ArrowRight: () => <span>arrow</span>,
|
||||
}));
|
||||
|
||||
import LandingScreen from '../screens/LandingScreen';
|
||||
|
||||
describe('LandingScreen', () => {
|
||||
it('renders join panel copy', () => {
|
||||
render(
|
||||
<LocaleProvider>
|
||||
<LandingScreen />
|
||||
</LocaleProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByRole('button', { name: 'Event beitreten' })).toBeInTheDocument();
|
||||
expect(screen.getAllByText('Event beitreten').length).toBeGreaterThan(1);
|
||||
expect(screen.getByPlaceholderText('Event-Code eingeben')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
28
resources/js/guest-v2/__tests__/NotFoundScreen.test.tsx
Normal file
28
resources/js/guest-v2/__tests__/NotFoundScreen.test.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
vi.mock('@tamagui/stacks', () => ({
|
||||
YStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/text', () => ({
|
||||
SizableText: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (_key: string, fallback?: string) => fallback ?? _key,
|
||||
}),
|
||||
}));
|
||||
|
||||
import NotFoundScreen from '../screens/NotFoundScreen';
|
||||
|
||||
describe('NotFoundScreen', () => {
|
||||
it('renders fallback copy', () => {
|
||||
render(<NotFoundScreen />);
|
||||
|
||||
expect(screen.getByText('Seite nicht gefunden')).toBeInTheDocument();
|
||||
expect(screen.getByText('Die Seite konnte nicht gefunden werden.')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
70
resources/js/guest-v2/__tests__/PhotoLightboxScreen.test.tsx
Normal file
70
resources/js/guest-v2/__tests__/PhotoLightboxScreen.test.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
vi.mock('react-router-dom', () => ({
|
||||
useParams: () => ({ photoId: '123' }),
|
||||
useNavigate: () => vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/stacks', () => ({
|
||||
YStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
XStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/text', () => ({
|
||||
SizableText: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/button', () => ({
|
||||
Button: ({ children, ...rest }: { children: React.ReactNode }) => (
|
||||
<button type="button" {...rest}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('../components/AppShell', () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../components/SurfaceCard', () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../context/EventDataContext', () => ({
|
||||
useEventData: () => ({ token: 'token' }),
|
||||
}));
|
||||
|
||||
vi.mock('../services/photosApi', () => ({
|
||||
fetchGallery: vi.fn().mockResolvedValue({ data: [], next_cursor: null, latest_photo_at: null }),
|
||||
fetchPhoto: vi.fn().mockResolvedValue({ id: 123, file_path: 'storage/demo.jpg', likes_count: 5 }),
|
||||
likePhoto: vi.fn().mockResolvedValue(6),
|
||||
createPhotoShareLink: vi.fn().mockResolvedValue({ url: 'http://example.com' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string, arg2?: Record<string, string | number> | string, arg3?: string) =>
|
||||
typeof arg2 === 'string' || arg2 === undefined ? (arg2 ?? arg3 ?? key) : (arg3 ?? key),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/LocaleContext', () => ({
|
||||
useLocale: () => ({ locale: 'de' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/hooks/use-appearance', () => ({
|
||||
useAppearance: () => ({ resolved: 'light' }),
|
||||
}));
|
||||
|
||||
import PhotoLightboxScreen from '../screens/PhotoLightboxScreen';
|
||||
|
||||
describe('PhotoLightboxScreen', () => {
|
||||
it('renders lightbox layout', async () => {
|
||||
render(<PhotoLightboxScreen />);
|
||||
|
||||
expect(await screen.findByText('Gallery')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Like')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
155
resources/js/guest-v2/__tests__/ScreensCopy.test.tsx
Normal file
155
resources/js/guest-v2/__tests__/ScreensCopy.test.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
import React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { EventDataProvider } from '../context/EventDataContext';
|
||||
|
||||
vi.mock('@tamagui/stacks', () => ({
|
||||
YStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
XStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/text', () => ({
|
||||
SizableText: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/button', () => ({
|
||||
Button: ({ children, ...rest }: { children: React.ReactNode }) => (
|
||||
<button type="button" {...rest}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/sheet', () => {
|
||||
const Sheet = ({ children }: { children: React.ReactNode }) => <div>{children}</div>;
|
||||
Sheet.Overlay = ({ children }: { children?: React.ReactNode }) => <div>{children}</div>;
|
||||
Sheet.Frame = ({ children }: { children?: React.ReactNode }) => <div>{children}</div>;
|
||||
Sheet.Handle = ({ children }: { children?: React.ReactNode }) => <div>{children}</div>;
|
||||
return { Sheet };
|
||||
});
|
||||
|
||||
vi.mock('react-router-dom', () => ({
|
||||
useNavigate: () => vi.fn(),
|
||||
useSearchParams: () => [new URLSearchParams()],
|
||||
}));
|
||||
|
||||
vi.mock('lucide-react', () => ({
|
||||
Image: () => <span>image</span>,
|
||||
Filter: () => <span>filter</span>,
|
||||
Camera: () => <span>camera</span>,
|
||||
Grid2x2: () => <span>grid</span>,
|
||||
Zap: () => <span>zap</span>,
|
||||
UploadCloud: () => <span>upload</span>,
|
||||
ListVideo: () => <span>list</span>,
|
||||
RefreshCcw: () => <span>refresh</span>,
|
||||
FlipHorizontal: () => <span>flip</span>,
|
||||
X: () => <span>close</span>,
|
||||
Sparkles: () => <span>sparkles</span>,
|
||||
Trophy: () => <span>trophy</span>,
|
||||
Play: () => <span>play</span>,
|
||||
Share2: () => <span>share</span>,
|
||||
QrCode: () => <span>qr</span>,
|
||||
Link: () => <span>link</span>,
|
||||
Users: () => <span>users</span>,
|
||||
}));
|
||||
|
||||
vi.mock('../components/AppShell', () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../context/EventDataContext', () => ({
|
||||
EventDataProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
useEventData: () => ({ token: 'demo', tasksEnabled: true, event: null }),
|
||||
}));
|
||||
|
||||
vi.mock('../services/uploadApi', () => ({
|
||||
useUploadQueue: () => ({ items: [], loading: false, add: vi.fn() }),
|
||||
uploadPhoto: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/services/pendingUploadsApi', () => ({
|
||||
fetchPendingUploadsSummary: vi.fn().mockResolvedValue({ items: [], totalCount: 0 }),
|
||||
}));
|
||||
|
||||
vi.mock('../services/photosApi', () => ({
|
||||
fetchGallery: vi.fn().mockResolvedValue({ data: [], next_cursor: null, latest_photo_at: null, notModified: false }),
|
||||
}));
|
||||
|
||||
vi.mock('../hooks/usePollGalleryDelta', () => ({
|
||||
usePollGalleryDelta: () => ({ data: { photos: [], latestPhotoAt: null, nextCursor: null }, loading: false, error: null }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string, arg2?: Record<string, string | number> | string, arg3?: string) =>
|
||||
typeof arg2 === 'string' || arg2 === undefined ? (arg2 ?? arg3 ?? key) : (arg3 ?? key),
|
||||
locale: 'de',
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/LocaleContext', () => ({
|
||||
useLocale: () => ({ locale: 'de', availableLocales: [], setLocale: vi.fn() }),
|
||||
}));
|
||||
|
||||
vi.mock('@/hooks/use-appearance', () => ({
|
||||
useAppearance: () => ({ resolved: 'light' }),
|
||||
}));
|
||||
|
||||
vi.mock('../services/tasksApi', () => ({
|
||||
fetchTasks: vi.fn().mockResolvedValue([]),
|
||||
}));
|
||||
|
||||
vi.mock('../services/emotionsApi', () => ({
|
||||
fetchEmotions: vi.fn().mockResolvedValue([]),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/hooks/useGuestTaskProgress', () => ({
|
||||
useGuestTaskProgress: () => ({ completedCount: 0 }),
|
||||
}));
|
||||
|
||||
import GalleryScreen from '../screens/GalleryScreen';
|
||||
import UploadScreen from '../screens/UploadScreen';
|
||||
import TasksScreen from '../screens/TasksScreen';
|
||||
import ShareScreen from '../screens/ShareScreen';
|
||||
|
||||
describe('Guest v2 screens copy', () => {
|
||||
it('renders gallery header', () => {
|
||||
render(
|
||||
<EventDataProvider tasksEnabledFallback>
|
||||
<GalleryScreen />
|
||||
</EventDataProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Gallery')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders upload preview prompt', () => {
|
||||
render(
|
||||
<EventDataProvider tasksEnabledFallback>
|
||||
<UploadScreen />
|
||||
</EventDataProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Camera')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders tasks quest when enabled', () => {
|
||||
render(
|
||||
<EventDataProvider tasksEnabledFallback>
|
||||
<TasksScreen />
|
||||
</EventDataProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Prompt quest')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders share hub header', () => {
|
||||
render(
|
||||
<EventDataProvider tasksEnabledFallback>
|
||||
<ShareScreen />
|
||||
</EventDataProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Invite guests')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
124
resources/js/guest-v2/__tests__/SettingsContent.test.tsx
Normal file
124
resources/js/guest-v2/__tests__/SettingsContent.test.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
|
||||
const updateAppearance = vi.fn();
|
||||
|
||||
vi.mock('@/hooks/use-appearance', () => ({
|
||||
useAppearance: () => ({ appearance: 'dark', updateAppearance }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({ t: (_key: string, fallback?: string) => fallback ?? _key }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/LocaleContext', () => ({
|
||||
useLocale: () => ({ locale: 'de', availableLocales: [], setLocale: vi.fn() }),
|
||||
}));
|
||||
|
||||
vi.mock('../context/EventDataContext', () => ({
|
||||
useEventData: () => ({ token: 'demo-token' }),
|
||||
}));
|
||||
|
||||
vi.mock('../context/GuestIdentityContext', () => ({
|
||||
useOptionalGuestIdentity: () => ({ hydrated: false, name: '', setName: vi.fn(), clearName: vi.fn() }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/hooks/useHapticsPreference', () => ({
|
||||
useHapticsPreference: () => ({ enabled: false, setEnabled: vi.fn(), supported: true }),
|
||||
}));
|
||||
|
||||
vi.mock('@/contexts/consent', () => ({
|
||||
useConsent: () => ({ preferences: { analytics: false }, savePreferences: vi.fn() }),
|
||||
}));
|
||||
|
||||
vi.mock('react-router-dom', () => ({
|
||||
Link: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/stacks', () => ({
|
||||
YStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
XStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/text', () => ({
|
||||
SizableText: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/button', () => ({
|
||||
Button: ({
|
||||
children,
|
||||
onPress,
|
||||
...rest
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
onPress?: () => void;
|
||||
}) => (
|
||||
<button type="button" onClick={onPress} {...rest}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/input', () => ({
|
||||
Input: ({
|
||||
value,
|
||||
onChange,
|
||||
onChangeText,
|
||||
...rest
|
||||
}: React.InputHTMLAttributes<HTMLInputElement> & { onChangeText?: (next: string) => void }) => (
|
||||
<input
|
||||
value={value}
|
||||
onChange={(event) => {
|
||||
onChange?.(event);
|
||||
onChangeText?.(event.target.value);
|
||||
}}
|
||||
readOnly={!onChange && !onChangeText}
|
||||
{...rest}
|
||||
/>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/card', () => ({
|
||||
Card: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/switch', () => ({
|
||||
Switch: Object.assign(
|
||||
({
|
||||
checked,
|
||||
onCheckedChange,
|
||||
'aria-label': ariaLabel,
|
||||
children,
|
||||
}: {
|
||||
checked?: boolean;
|
||||
onCheckedChange?: (next: boolean) => void;
|
||||
'aria-label'?: string;
|
||||
children?: React.ReactNode;
|
||||
}) => (
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-label={ariaLabel}
|
||||
checked={checked}
|
||||
onChange={(event) => onCheckedChange?.(event.target.checked)}
|
||||
/>
|
||||
{children}
|
||||
</label>
|
||||
),
|
||||
{ Thumb: ({ children }: { children?: React.ReactNode }) => <span>{children}</span> },
|
||||
),
|
||||
}));
|
||||
|
||||
import SettingsContent from '../components/SettingsContent';
|
||||
|
||||
describe('SettingsContent', () => {
|
||||
it('toggles appearance mode', () => {
|
||||
render(<SettingsContent />);
|
||||
|
||||
const toggle = screen.getByLabelText('Dark mode');
|
||||
fireEvent.click(toggle);
|
||||
|
||||
expect(updateAppearance).toHaveBeenCalledWith('light');
|
||||
});
|
||||
});
|
||||
58
resources/js/guest-v2/__tests__/SettingsSheet.test.tsx
Normal file
58
resources/js/guest-v2/__tests__/SettingsSheet.test.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({ t: (_key: string, fallback?: string) => fallback ?? _key }),
|
||||
}));
|
||||
|
||||
vi.mock('@/hooks/use-appearance', () => ({
|
||||
useAppearance: () => ({ resolved: 'dark' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/LocaleContext', () => ({
|
||||
useLocale: () => ({ locale: 'de' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/components/legal-markdown', () => ({
|
||||
LegalMarkdown: () => <div>Legal markdown</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/scroll-view', () => ({
|
||||
ScrollView: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/stacks', () => ({
|
||||
YStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
XStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/text', () => ({
|
||||
SizableText: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/button', () => ({
|
||||
Button: ({ children, ...rest }: { children: React.ReactNode }) => (
|
||||
<button type="button" {...rest}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('lucide-react', () => ({
|
||||
X: () => <span>x</span>,
|
||||
}));
|
||||
|
||||
vi.mock('../components/SettingsContent', () => ({
|
||||
default: () => <div>Settings content</div>,
|
||||
}));
|
||||
|
||||
import SettingsSheet from '../components/SettingsSheet';
|
||||
|
||||
describe('SettingsSheet', () => {
|
||||
it('renders settings content inside the sheet', () => {
|
||||
render(<SettingsSheet open onOpenChange={vi.fn()} />);
|
||||
|
||||
expect(screen.getByText('Settings content')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
31
resources/js/guest-v2/__tests__/SlideshowScreen.test.tsx
Normal file
31
resources/js/guest-v2/__tests__/SlideshowScreen.test.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { EventDataProvider } from '../context/EventDataContext';
|
||||
|
||||
vi.mock('framer-motion', () => ({
|
||||
AnimatePresence: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
motion: { div: ({ children }: { children: React.ReactNode }) => <div>{children}</div> },
|
||||
}));
|
||||
|
||||
vi.mock('../services/photosApi', () => ({
|
||||
fetchGallery: () => Promise.resolve({ data: [] }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({ t: (_key: string, fallback?: string) => fallback ?? _key, locale: 'de' }),
|
||||
}));
|
||||
|
||||
import SlideshowScreen from '../screens/SlideshowScreen';
|
||||
|
||||
describe('SlideshowScreen', () => {
|
||||
it('shows empty state when no photos', async () => {
|
||||
render(
|
||||
<EventDataProvider token="token">
|
||||
<SlideshowScreen />
|
||||
</EventDataProvider>
|
||||
);
|
||||
|
||||
expect(await screen.findByText('Noch keine Fotos')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
60
resources/js/guest-v2/__tests__/TaskDetailScreen.test.tsx
Normal file
60
resources/js/guest-v2/__tests__/TaskDetailScreen.test.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { EventDataProvider } from '../context/EventDataContext';
|
||||
|
||||
vi.mock('react-router-dom', () => ({
|
||||
useParams: () => ({ taskId: '12' }),
|
||||
useNavigate: () => vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/stacks', () => ({
|
||||
YStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
XStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/text', () => ({
|
||||
SizableText: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/button', () => ({
|
||||
Button: ({ children, ...rest }: { children: React.ReactNode }) => (
|
||||
<button type="button" {...rest}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('../components/AppShell', () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../components/SurfaceCard', () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../services/tasksApi', () => ({
|
||||
fetchTasks: () => Promise.resolve([{ id: 12, title: 'Capture the dancefloor', description: 'Find the happiest crew.' }]),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({ t: (_key: string, fallback?: string) => fallback ?? _key, locale: 'de' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/hooks/use-appearance', () => ({
|
||||
useAppearance: () => ({ resolved: 'light' }),
|
||||
}));
|
||||
|
||||
import TaskDetailScreen from '../screens/TaskDetailScreen';
|
||||
|
||||
describe('TaskDetailScreen', () => {
|
||||
it('renders task title', async () => {
|
||||
render(
|
||||
<EventDataProvider token="token">
|
||||
<TaskDetailScreen />
|
||||
</EventDataProvider>
|
||||
);
|
||||
|
||||
expect(await screen.findByText('Capture the dancefloor')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
72
resources/js/guest-v2/__tests__/UploadQueueScreen.test.tsx
Normal file
72
resources/js/guest-v2/__tests__/UploadQueueScreen.test.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
vi.mock('@tamagui/stacks', () => ({
|
||||
YStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
XStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/text', () => ({
|
||||
SizableText: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/button', () => ({
|
||||
Button: ({ children, ...rest }: { children: React.ReactNode }) => (
|
||||
<button type="button" {...rest}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('../components/AppShell', () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../components/SurfaceCard', () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../services/uploadApi', () => ({
|
||||
useUploadQueue: () => ({
|
||||
items: [],
|
||||
loading: false,
|
||||
retryAll: vi.fn(),
|
||||
clearFinished: vi.fn(),
|
||||
refresh: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('../context/EventDataContext', () => ({
|
||||
useEventData: () => ({ token: 'token' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/services/pendingUploadsApi', () => ({
|
||||
fetchPendingUploadsSummary: vi.fn().mockResolvedValue({ items: [], totalCount: 0 }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string, arg2?: Record<string, string | number> | string, arg3?: string) =>
|
||||
typeof arg2 === 'string' || arg2 === undefined ? (arg2 ?? arg3 ?? key) : (arg3 ?? key),
|
||||
locale: 'de',
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/LocaleContext', () => ({
|
||||
useLocale: () => ({ locale: 'de' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/hooks/use-appearance', () => ({
|
||||
useAppearance: () => ({ resolved: 'light' }),
|
||||
}));
|
||||
|
||||
import UploadQueueScreen from '../screens/UploadQueueScreen';
|
||||
|
||||
describe('UploadQueueScreen', () => {
|
||||
it('renders empty queue state', () => {
|
||||
render(<UploadQueueScreen />);
|
||||
|
||||
expect(screen.getByText('Uploads')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
87
resources/js/guest-v2/__tests__/UploadScreen.test.tsx
Normal file
87
resources/js/guest-v2/__tests__/UploadScreen.test.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { EventDataProvider } from '../context/EventDataContext';
|
||||
|
||||
vi.mock('react-router-dom', () => ({
|
||||
useNavigate: () => vi.fn(),
|
||||
useSearchParams: () => [new URLSearchParams('taskId=12')],
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/stacks', () => ({
|
||||
YStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
XStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/text', () => ({
|
||||
SizableText: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/button', () => ({
|
||||
Button: ({ children, ...rest }: { children: React.ReactNode }) => (
|
||||
<button type="button" {...rest}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('../components/AppShell', () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../services/uploadApi', () => ({
|
||||
uploadPhoto: vi.fn(),
|
||||
useUploadQueue: () => ({ items: [], add: vi.fn() }),
|
||||
}));
|
||||
|
||||
vi.mock('../services/tasksApi', () => ({
|
||||
fetchTasks: vi.fn().mockResolvedValue([{ id: 12, title: 'Capture the dancefloor', description: 'Find the happiest crew.' }]),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/services/pendingUploadsApi', () => ({
|
||||
fetchPendingUploadsSummary: vi.fn().mockResolvedValue({ items: [], totalCount: 0 }),
|
||||
}));
|
||||
|
||||
vi.mock('../context/GuestIdentityContext', () => ({
|
||||
useOptionalGuestIdentity: () => ({ name: 'Alex' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/hooks/useGuestTaskProgress', () => ({
|
||||
useGuestTaskProgress: () => ({ markCompleted: vi.fn() }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (_key: string, arg2?: Record<string, string | number> | string, arg3?: string) =>
|
||||
typeof arg2 === 'string' || arg2 === undefined ? (arg2 ?? arg3 ?? _key) : (arg3 ?? _key),
|
||||
locale: 'en',
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@/hooks/use-appearance', () => ({
|
||||
useAppearance: () => ({ resolved: 'light' }),
|
||||
}));
|
||||
|
||||
import UploadScreen from '../screens/UploadScreen';
|
||||
|
||||
describe('UploadScreen', () => {
|
||||
it('renders queue entry point', () => {
|
||||
render(
|
||||
<EventDataProvider token="token">
|
||||
<UploadScreen />
|
||||
</EventDataProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Queue')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders task summary when taskId is present', async () => {
|
||||
render(
|
||||
<EventDataProvider token="token">
|
||||
<UploadScreen />
|
||||
</EventDataProvider>
|
||||
);
|
||||
|
||||
expect(await screen.findByText('Capture the dancefloor')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
74
resources/js/guest-v2/__tests__/brandingTheme.test.ts
Normal file
74
resources/js/guest-v2/__tests__/brandingTheme.test.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { describe, expect, it, beforeEach, afterEach } from 'vitest';
|
||||
import { resolveGuestThemeName } from '../lib/brandingTheme';
|
||||
import type { EventBranding } from '@/guest/types/event-branding';
|
||||
|
||||
const baseBranding: EventBranding = {
|
||||
primaryColor: '#FF5A5F',
|
||||
secondaryColor: '#F43F5E',
|
||||
backgroundColor: '#ffffff',
|
||||
fontFamily: 'Inter',
|
||||
logoUrl: null,
|
||||
palette: {
|
||||
primary: '#FF5A5F',
|
||||
secondary: '#F43F5E',
|
||||
background: '#ffffff',
|
||||
surface: '#ffffff',
|
||||
},
|
||||
typography: {
|
||||
heading: 'Inter',
|
||||
body: 'Inter',
|
||||
sizePreset: 'm',
|
||||
},
|
||||
mode: 'auto',
|
||||
};
|
||||
|
||||
const originalMatchMedia = window.matchMedia;
|
||||
|
||||
function mockMatchMedia(matches: boolean) {
|
||||
window.matchMedia = ((query: string) => ({
|
||||
matches,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addEventListener: () => {},
|
||||
removeEventListener: () => {},
|
||||
addListener: () => {},
|
||||
removeListener: () => {},
|
||||
dispatchEvent: () => false,
|
||||
})) as typeof window.matchMedia;
|
||||
}
|
||||
|
||||
describe('resolveGuestThemeName', () => {
|
||||
beforeEach(() => {
|
||||
mockMatchMedia(false);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.matchMedia = originalMatchMedia;
|
||||
});
|
||||
|
||||
it('uses branding mode overrides', () => {
|
||||
expect(resolveGuestThemeName({ ...baseBranding, mode: 'dark' }, 'light')).toBe('guestNight');
|
||||
expect(resolveGuestThemeName({ ...baseBranding, mode: 'light' }, 'dark')).toBe('guestLight');
|
||||
});
|
||||
|
||||
it('respects explicit appearance when mode is auto', () => {
|
||||
expect(resolveGuestThemeName({ ...baseBranding, mode: 'auto' }, 'dark')).toBe('guestNight');
|
||||
expect(resolveGuestThemeName({ ...baseBranding, mode: 'auto' }, 'light')).toBe('guestLight');
|
||||
});
|
||||
|
||||
it('falls back to background luminance when appearance is system', () => {
|
||||
const darkBackground = { ...baseBranding, backgroundColor: '#0a0f1f' };
|
||||
expect(resolveGuestThemeName(darkBackground, 'system')).toBe('guestNight');
|
||||
|
||||
const lightBackground = { ...baseBranding, backgroundColor: '#fdf9f4' };
|
||||
expect(resolveGuestThemeName(lightBackground, 'system')).toBe('guestLight');
|
||||
});
|
||||
|
||||
it('uses system preference when background is neutral', () => {
|
||||
const neutralBackground = { ...baseBranding, backgroundColor: '#b0b0b0' };
|
||||
mockMatchMedia(true);
|
||||
expect(resolveGuestThemeName(neutralBackground, 'system')).toBe('guestNight');
|
||||
mockMatchMedia(false);
|
||||
expect(resolveGuestThemeName(neutralBackground, 'system')).toBe('guestLight');
|
||||
});
|
||||
});
|
||||
31
resources/js/guest-v2/__tests__/eventBranding.test.ts
Normal file
31
resources/js/guest-v2/__tests__/eventBranding.test.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { mapEventBranding } from '../lib/eventBranding';
|
||||
|
||||
describe('mapEventBranding', () => {
|
||||
it('maps palette, typography, and buttons from payload', () => {
|
||||
const result = mapEventBranding({
|
||||
primary_color: '#112233',
|
||||
secondary_color: '#445566',
|
||||
background_color: '#000000',
|
||||
font_family: 'Event Body',
|
||||
heading_font: 'Event Heading',
|
||||
button_radius: 16,
|
||||
button_primary_color: '#abcdef',
|
||||
palette: {
|
||||
surface: '#111111',
|
||||
},
|
||||
typography: {
|
||||
size: 'l',
|
||||
},
|
||||
});
|
||||
|
||||
expect(result?.primaryColor).toBe('#112233');
|
||||
expect(result?.secondaryColor).toBe('#445566');
|
||||
expect(result?.palette?.surface).toBe('#111111');
|
||||
expect(result?.typography?.heading).toBe('Event Heading');
|
||||
expect(result?.typography?.body).toBe('Event Body');
|
||||
expect(result?.typography?.sizePreset).toBe('l');
|
||||
expect(result?.buttons?.radius).toBe(16);
|
||||
expect(result?.buttons?.primary).toBe('#abcdef');
|
||||
});
|
||||
});
|
||||
33
resources/js/guest-v2/__tests__/statsApi.test.ts
Normal file
33
resources/js/guest-v2/__tests__/statsApi.test.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { fetchEventStats, clearStatsCache } from '../services/statsApi';
|
||||
|
||||
const fetchMock = vi.fn();
|
||||
|
||||
global.fetch = fetchMock as unknown as typeof fetch;
|
||||
|
||||
describe('fetchEventStats', () => {
|
||||
beforeEach(() => {
|
||||
fetchMock.mockReset();
|
||||
clearStatsCache();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clearStatsCache();
|
||||
});
|
||||
|
||||
it('returns cached stats on 304', async () => {
|
||||
fetchMock.mockResolvedValueOnce(
|
||||
new Response(JSON.stringify({ online_guests: 4, tasks_solved: 1, latest_photo_at: '2024-01-01T00:00:00Z' }), {
|
||||
status: 200,
|
||||
headers: { ETag: '"demo"' },
|
||||
})
|
||||
);
|
||||
|
||||
const first = await fetchEventStats('demo');
|
||||
expect(first.onlineGuests).toBe(4);
|
||||
|
||||
fetchMock.mockResolvedValueOnce(new Response(null, { status: 304, headers: { ETag: '"demo"' } }));
|
||||
const second = await fetchEventStats('demo');
|
||||
expect(second).toEqual(first);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user