From accc63f4a2a15693c6320de68107b45dad27a7ac Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Mon, 12 Jan 2026 10:46:18 +0100 Subject: [PATCH] Add pending test files --- .../components/__tests__/MobileShell.test.tsx | 143 ++++++++++++++++++ tests/Unit/RateLimitConfigTest.php | 67 ++++++++ 2 files changed, 210 insertions(+) create mode 100644 resources/js/admin/mobile/components/__tests__/MobileShell.test.tsx create mode 100644 tests/Unit/RateLimitConfigTest.php diff --git a/resources/js/admin/mobile/components/__tests__/MobileShell.test.tsx b/resources/js/admin/mobile/components/__tests__/MobileShell.test.tsx new file mode 100644 index 0000000..e651285 --- /dev/null +++ b/resources/js/admin/mobile/components/__tests__/MobileShell.test.tsx @@ -0,0 +1,143 @@ +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'; + +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('../BottomNav', () => ({ + BottomNav: () =>
, + NavKey: {}, +})); + +vi.mock('../../../context/EventContext', () => ({ + useEventContext: () => ({ + events: [], + activeEvent: { slug: 'event-1', name: 'Test Event', event_date: '2024-01-01', status: 'active', settings: {} }, + hasMultipleEvents: false, + hasEvents: true, + selectEvent: vi.fn(), + }), +})); + +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)', + }), +})); + +import { MobileShell } from '../MobileShell'; + +describe('MobileShell', () => { + beforeEach(() => { + window.matchMedia = vi.fn().mockReturnValue({ + matches: false, + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + }); + }); + + 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(); + }); +}); diff --git a/tests/Unit/RateLimitConfigTest.php b/tests/Unit/RateLimitConfigTest.php new file mode 100644 index 0000000..5f4a14d --- /dev/null +++ b/tests/Unit/RateLimitConfigTest.php @@ -0,0 +1,67 @@ + '10.0.0.1', + ]); + $request->attributes->set('tenant_id', 42); + + $limiter = RateLimiter::limiter('tenant-api'); + + $this->assertNotNull($limiter); + + $limit = $limiter($request); + + $this->assertInstanceOf(Limit::class, $limit); + $this->assertSame(600, $limit->maxAttempts); + } + + public function test_guest_api_rate_limiter_allows_higher_throughput(): void + { + $request = Request::create('/api/v1/events/sample', 'GET', [], [], [], [ + 'REMOTE_ADDR' => '10.0.0.2', + ]); + + $limiter = RateLimiter::limiter('guest-api'); + + $this->assertNotNull($limiter); + + $limit = $limiter($request); + + $this->assertInstanceOf(Limit::class, $limit); + $this->assertSame(300, $limit->maxAttempts); + } + + public function test_guest_policy_defaults_follow_join_token_limits(): void + { + $accessLimit = 300; + $downloadLimit = 120; + + config([ + 'join_tokens.access_limit' => $accessLimit, + 'join_tokens.download_limit' => $downloadLimit, + ]); + + GuestPolicySetting::query()->delete(); + GuestPolicySetting::flushCache(); + + $settings = GuestPolicySetting::current(); + + $this->assertSame($accessLimit, $settings->join_token_access_limit); + $this->assertSame($downloadLimit, $settings->join_token_download_limit); + } +}