import React from 'react'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import GroupRidesPage from '../app/group-rides/page'; import { LivePackRadar } from '../components/group-rides/live-pack-radar'; import { JoinRideModal } from '../components/group-rides/join-ride-modal'; import { CreateRideModal } from '../components/group-rides/create-ride-modal'; import { GroupRidesDirectory } from '../components/group-rides/group-rides-directory'; import { INITIAL_GROUP_RIDES } from '../components/group-rides/fixtures'; import { SiteHeaderClient } from '../components/site-header-client'; vi.mock('next/navigation', () => ({ usePathname: () => '/', useRouter: () => ({ push: vi.fn(), replace: vi.fn(), back: vi.fn() }), })); describe('Group Rides Interactive Page & Components', () => { beforeEach(() => { vi.restoreAllMocks(); }); // ============================================================ // 1. LivePackRadar Component Tests // ============================================================ describe('LivePackRadar', () => { it('renders radar HUD, default tactical view, and all 6 rider telemetry cards', () => { render(); // Verify header and HUD expect(screen.getByText(/Swarm Recon: Skyline Blvd > Coast Hwy/i)).toBeDefined(); expect(screen.getByText(/Session #CA-84-SW/i)).toBeDefined(); expect(screen.getByText(/6 Synchronized/i)).toBeDefined(); // Verify GPS Frequency is NOT present, but GNSS Multi-Band Mesh IS present expect(screen.queryByText(/GPS Frequency/i)).toBeNull(); expect(screen.getByText(/GNSS Multi-Band Mesh/i)).toBeDefined(); expect(screen.getByText(/18 Satellites Locked/i)).toBeDefined(); // Verify all riders in the stream expect(screen.getByTestId('telemetry-card-ApexLead')).toBeDefined(); expect(screen.getByTestId('telemetry-card-Ghost99')).toBeDefined(); expect(screen.getByTestId('telemetry-card-Sarah_RR')).toBeDefined(); expect(screen.getByTestId('telemetry-card-Dan_KTM')).toBeDefined(); expect(screen.getByTestId('telemetry-card-Alex_Ducati')).toBeDefined(); expect(screen.getByTestId('telemetry-card-Sweep_Marshal')).toBeDefined(); // Verify alert text expect(screen.getByText(/Rear tire 28 PSI \(low\)/i)).toBeDefined(); expect(screen.getByText(/Hard Braking \(-1.2G\) ABS Engaged/i)).toBeDefined(); }); it('allows toggling between Tactical, Satellite, and Elevation 3D modes', () => { render(); const tacticalBtn = screen.getByRole('button', { name: /^tactical$/i }); const satelliteBtn = screen.getByRole('button', { name: /^satellite$/i }); const elevationBtn = screen.getByRole('button', { name: /^elevation 3d$/i }); // Click Satellite fireEvent.click(satelliteBtn); expect(screen.getByTestId('satellite-mode-view')).toBeDefined(); expect(screen.getByText(/High-Resolution Orbit Imagery/i)).toBeDefined(); // Click Elevation 3D fireEvent.click(elevationBtn); expect(screen.getByTestId('elevation-mode-view')).toBeDefined(); expect(screen.getByText(/Summit: 2,210 ft/i)).toBeDefined(); // Return to Tactical fireEvent.click(tacticalBtn); expect(screen.queryByTestId('satellite-mode-view')).toBeNull(); expect(screen.queryByTestId('elevation-mode-view')).toBeNull(); }); it('allows selecting riders from telemetry column or canvas markers', () => { render(); // Default selected is ApexLead expect(screen.getByText(/Selected Node Inspector/i)).toBeDefined(); // Select Alex_Ducati via telemetry card const alexCard = screen.getByTestId('telemetry-card-Alex_Ducati'); fireEvent.click(alexCard); // Verify inspector reflects Alex_Ducati const inspector = screen.getByTestId('selected-node-inspector'); expect(inspector.textContent).toContain('Alex_Ducati'); expect(inspector.textContent).toContain('Decel Event'); // Select Ghost99 via canvas marker const ghostMarker = screen.getByTestId('radar-marker-Ghost99'); fireEvent.click(ghostMarker); expect(inspector.textContent).toContain('Ghost99'); }); it('animates/wobbles speed on ping swarm button click', () => { render(); const pingBtn = screen.getByRole('button', { name: /ping swarm/i }); fireEvent.click(pingBtn); // Still renders active telemetry correctly expect(screen.getByText(/6 Synchronized/i)).toBeDefined(); }); }); // ============================================================ // 2. JoinRideModal Component Tests // ============================================================ describe('JoinRideModal', () => { it('handles open/close lifecycle and Escape key', () => { const onClose = vi.fn(); const onJoinSuccess = vi.fn(); const { rerender } = render( ); expect(screen.queryByRole('dialog')).toBeNull(); rerender(); expect(screen.getByRole('dialog')).toBeDefined(); fireEvent.keyDown(window, { key: 'Escape' }); expect(onClose).toHaveBeenCalled(); }); it('validates short ride code and rejects submission with error', () => { const onClose = vi.fn(); const onJoinSuccess = vi.fn(); render(); const submitBtn = screen.getByRole('button', { name: /connect squad telemetry/i }); fireEvent.click(submitBtn); expect(screen.getByText(/Please enter a valid ride code/i)).toBeDefined(); expect(onJoinSuccess).not.toHaveBeenCalled(); }); it('activates radar lens camera and simulates QR scan successfully', async () => { const onClose = vi.fn(); const onJoinSuccess = vi.fn(); render(); // Click activate optical lens const lensTrigger = screen.getByTestId('activate-radar-lens-button'); fireEvent.click(lensTrigger); expect(screen.getByTestId('radar-lens-viewfinder')).toBeDefined(); // Click simulate QR scan const simulateScanBtn = screen.getByRole('button', { name: /simulate qr scan/i }); fireEvent.click(simulateScanBtn); await waitFor(() => { expect(screen.getByText(/Telemetry Linked/i)).toBeDefined(); }); expect(screen.getByText(/Bay Area Dawn Patrol/i)).toBeDefined(); expect(onJoinSuccess).toHaveBeenCalledWith('APEX84', 'Bay Area Dawn Patrol'); }); }); // ============================================================ // 3. CreateRideModal Component Tests // ============================================================ describe('CreateRideModal', () => { it('validates required fields before submitting', () => { const onClose = vi.fn(); const onCreateRide = vi.fn(); render(); const submitBtn = screen.getByRole('button', { name: /publish group ride/i }); fireEvent.click(submitBtn); expect(screen.getByText(/Please enter a ride title/i)).toBeDefined(); expect(onCreateRide).not.toHaveBeenCalled(); }); it('submits valid ride and invokes onCreateRide callback', () => { const onClose = vi.fn(); const onCreateRide = vi.fn(); render(); const titleInput = screen.getByLabelText(/ride title/i); const regionInput = screen.getByLabelText(/region \/ city/i); fireEvent.change(titleInput, { target: { value: 'Skyline Sunrise Blast' } }); fireEvent.change(regionInput, { target: { value: 'Palo Alto, CA' } }); const submitBtn = screen.getByRole('button', { name: /publish group ride/i }); fireEvent.click(submitBtn); expect(onCreateRide).toHaveBeenCalledWith( expect.objectContaining({ title: 'Skyline Sunrise Blast', region: 'Palo Alto, CA', userStatus: 'joined', }) ); expect(onClose).toHaveBeenCalled(); }); }); // ============================================================ // 4. GroupRidesDirectory Component Tests // ============================================================ describe('GroupRidesDirectory', () => { it('renders initial rides with correct pace tabs and badges', () => { render(); expect(screen.getByText(/Bay Area Dawn Patrol/i)).toBeDefined(); expect(screen.getByText(/Malibu Canyons Fast Pace/i)).toBeDefined(); expect(screen.getByText(/Tail of the Dragon Run/i)).toBeDefined(); // Pace tabs expect(screen.getByRole('button', { name: /all runs/i })).toBeDefined(); expect(screen.getByRole('button', { name: /brisk pace/i })).toBeDefined(); expect(screen.getByRole('button', { name: /track \/ apex/i })).toBeDefined(); expect(screen.getByRole('button', { name: /weekend tours/i })).toBeDefined(); }); it('filters rides by pace tab', () => { render(); // Click Track / Apex const trackBtn = screen.getByRole('button', { name: /track \/ apex/i }); fireEvent.click(trackBtn); expect(screen.getByText(/Bay Area Dawn Patrol/i)).toBeDefined(); expect(screen.queryByText(/Malibu Canyons Fast Pace/i)).toBeNull(); }); it('filters rides by search text and shows empty state when no match', () => { render(); const searchInput = screen.getByPlaceholderText(/search by region, canyon/i); fireEvent.change(searchInput, { target: { value: 'Robbinsville' } }); expect(screen.getByText(/Tail of the Dragon Run/i)).toBeDefined(); expect(screen.queryByText(/Bay Area Dawn Patrol/i)).toBeNull(); // Non-matching search fireEvent.change(searchInput, { target: { value: 'nonexistent-canyon-run' } }); expect(screen.getByText(/No Matching Pack Runs/i)).toBeDefined(); // Reset button restores const resetBtn = screen.getByRole('button', { name: /reset filters/i }); fireEvent.click(resetBtn); expect(screen.getByText(/Bay Area Dawn Patrol/i)).toBeDefined(); }); it('handles requesting a slot', () => { const onRequestSlot = vi.fn(); render(); const requestButtons = screen.getAllByRole('button', { name: /request pack slot/i }); expect(requestButtons.length).toBeGreaterThan(0); fireEvent.click(requestButtons[0]); expect(onRequestSlot).toHaveBeenCalled(); }); }); // ============================================================ // 5. Full GroupRidesPage Integrated Flow // ============================================================ describe('Full GroupRidesPage Integration', () => { it('coordinates create ride and join ride modals and alerts', async () => { render(); // Hero check expect(screen.getByText(/Ride in pack formation/i)).toBeDefined(); expect(screen.getByText(/Never lose a wingman/i)).toBeDefined(); // Open Create Modal from hero button const createHeroBtn = screen.getByRole('button', { name: /create group ride/i }); fireEvent.click(createHeroBtn); expect(screen.getByRole('heading', { name: /create group ride run/i })).toBeDefined(); // Fill and submit fireEvent.change(screen.getByLabelText(/ride title/i), { target: { value: 'Redwood Highway Sprint' }, }); fireEvent.change(screen.getByLabelText(/region \/ city/i), { target: { value: 'Eureka, CA' }, }); fireEvent.click(screen.getByRole('button', { name: /publish group ride/i })); // Directory should now show the new ride at the top expect(screen.getByRole('heading', { name: /Redwood Highway Sprint/i })).toBeDefined(); expect(screen.getByText(/Eureka, CA/i)).toBeDefined(); // Open Join Modal from hero button const joinHeroBtn = screen.getByRole('button', { name: /join with ride code \/ qr/i }); fireEvent.click(joinHeroBtn); expect(screen.getByRole('heading', { name: /join pack formation/i })).toBeDefined(); // Enter code and submit const codeInput = screen.getByLabelText(/ride code/i); fireEvent.change(codeInput, { target: { value: 'APEX84' } }); fireEvent.click(screen.getByRole('button', { name: /connect squad telemetry/i })); await waitFor(() => { expect(screen.getByText(/Telemetry Linked/i)).toBeDefined(); }); }); }); // ============================================================ // 6. Authenticated Navigation to Group Rides // ============================================================ describe('Authenticated Navigation to Group Rides', () => { it('renders Group Rides quick button and user dropdown link for signed-in users', () => { render(); // Quick header button for signed-in users const quickGroupRidesBtn = screen.getByTitle('Group Rides Hub'); expect(quickGroupRidesBtn).toBeDefined(); expect(quickGroupRidesBtn.getAttribute('href')).toBe('/group-rides'); // Open Rider Menu dropdown const riderMenuBtn = screen.getByRole('button', { name: /open rider menu/i }); fireEvent.click(riderMenuBtn); // Verify Group Rides link exists inside dropdown menu const groupRidesLinks = screen.getAllByRole('menuitem', { name: /group rides/i }); expect(groupRidesLinks.length).toBeGreaterThan(0); expect(groupRidesLinks[0].getAttribute('href')).toBe('/group-rides'); }); it('renders Group Rides in mobile navigation drawer for signed-in users', () => { render(); const hamburgerBtn = screen.getByRole('button', { name: /open navigation menu/i }); fireEvent.click(hamburgerBtn); const groupRidesNavLinks = screen.getAllByRole('link', { name: /group rides/i }); const mobileDrawerLink = groupRidesNavLinks.find(link => link.getAttribute('href') === '/group-rides'); expect(mobileDrawerLink).toBeDefined(); }); }); });