81 lines
5.3 KiB
TypeScript
81 lines
5.3 KiB
TypeScript
import React from 'react';
|
|
import { act, cleanup, render, screen, waitFor } from '@testing-library/react';
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { LivePackRadar } from '../components/group-rides/live-pack-radar';
|
|
import { GroupRidesManagement } from '../components/group-rides/group-rides-management';
|
|
|
|
const streams = vi.hoisted(() => ({ listeners: new Map<string, { next: (snapshot: unknown) => void; error: (error: unknown) => void; stop: ReturnType<typeof vi.fn> }>() }));
|
|
vi.mock('../lib/firebase-client', () => ({ firestore: {}, firebaseAuth: {} }));
|
|
vi.mock('firebase/app', () => ({ getApps: () => [{ name: 'motorsignal-local-qa' }], initializeApp: vi.fn() }));
|
|
vi.mock('firebase/auth', () => ({ getAuth: () => ({}), connectAuthEmulator: vi.fn(), signInWithEmailAndPassword: vi.fn(), signOut: vi.fn() }));
|
|
vi.mock('firebase/firestore', () => ({ getFirestore: () => ({}), connectFirestoreEmulator: vi.fn(),
|
|
collection: (_db: unknown, ...path: string[]) => path.join('/'),
|
|
where: () => ({}), query: (path: string) => `query:${path}`,
|
|
getDoc: async () => { throw Object.assign(new Error('permission-denied'), { code: 'permission-denied' }); },
|
|
doc: (_db: unknown, ...path: string[]) => path.join('/'),
|
|
onSnapshot: (path: string, next: (snapshot: unknown) => void, error: (error: unknown) => void) => {
|
|
const stop = vi.fn(); streams.listeners.set(path, { next, error, stop }); return stop;
|
|
},
|
|
}));
|
|
const ride = { id: 'ride', title: 'Isolated Ride', meetingPoint: 'QA Rally', participantCount: 2 } as any;
|
|
const snapshot = (data: unknown) => ({ exists: () => true, data: () => data });
|
|
const rideData = { organizerUid: 'owner', accessUids: ['owner', 'member'] };
|
|
|
|
function load() {
|
|
const view = render(<LivePackRadar selectedRide={ride} currentUser={{ uid: 'member' }} />);
|
|
act(() => streams.listeners.get('group_rides/ride')!.next(snapshot(rideData)));
|
|
act(() => streams.listeners.get('group_rides/ride/live_presence/owner')!.next(snapshot({ latitude: 34.5, longitude: -83.9,
|
|
speedMph: 40, updatedAt: new Date().toISOString(), sharingEnabled: true })));
|
|
expect(screen.getByTestId('telemetry-card-ApexLeader')).toBeDefined();
|
|
return view;
|
|
}
|
|
beforeEach(() => { streams.listeners.clear(); window.history.replaceState({}, '', '/'); });
|
|
afterEach(cleanup);
|
|
describe('radar stops exposing cached telemetry when access ends', () => {
|
|
it('clears cached cards and stops presence listeners when membership is revoked', () => {
|
|
load();
|
|
const presence = streams.listeners.get('group_rides/ride/live_presence/owner')!;
|
|
act(() => streams.listeners.get('group_rides/ride')!.next(snapshot({ organizerUid: 'owner', accessUids: ['owner'] })));
|
|
expect(screen.getByTestId('telemetry-access-barrier')).toBeDefined();
|
|
expect(screen.queryByTestId('telemetry-card-ApexLeader')).toBeNull();
|
|
expect(presence.stop).toHaveBeenCalledOnce();
|
|
act(() => presence.next(snapshot({ latitude: 34.5, longitude: -83.9, updatedAt: new Date().toISOString() })));
|
|
expect(screen.queryByTestId('telemetry-card-ApexLeader')).toBeNull();
|
|
});
|
|
it('clears cached cards and stops listeners after a denied presence snapshot', () => {
|
|
load();
|
|
const presence = streams.listeners.get('group_rides/ride/live_presence/owner')!;
|
|
act(() => presence.error({ code: 'permission-denied', message: 'permission-denied' }));
|
|
expect(screen.getByTestId('telemetry-access-barrier')).toBeDefined();
|
|
expect(screen.queryByTestId('telemetry-card-ApexLeader')).toBeNull();
|
|
expect(presence.stop).toHaveBeenCalledOnce();
|
|
});
|
|
it('unsubscribes ride and presence streams when leaving the radar tab', () => {
|
|
const view = load(); view.unmount();
|
|
for (const listener of streams.listeners.values()) expect(listener.stop).toHaveBeenCalledOnce();
|
|
});
|
|
it('shows a telemetry error and clears cached cards when the ride disappears', () => {
|
|
load();
|
|
const presence = streams.listeners.get('group_rides/ride/live_presence/owner')!;
|
|
act(() => streams.listeners.get('group_rides/ride')!.next({ exists: () => false }));
|
|
expect(screen.queryByTestId('telemetry-card-ApexLeader')).toBeNull();
|
|
expect(screen.getByTestId('presence-error-state').textContent).toContain('no longer available');
|
|
expect(presence.stop).toHaveBeenCalledOnce();
|
|
});
|
|
it('shows an error instead of an empty pack on unavailable presence streams', () => {
|
|
load();
|
|
act(() => streams.listeners.get('group_rides/ride/live_presence/owner')!.error({ code: 'unavailable', message: 'unavailable' }));
|
|
expect(screen.queryByTestId('telemetry-card-ApexLeader')).toBeNull();
|
|
expect(screen.getByTestId('presence-error-state')).toBeDefined();
|
|
expect(screen.queryByTestId('empty-pack-state')).toBeNull();
|
|
});
|
|
it('keeps an inaccessible selected ride in real radar mode instead of displaying a demo', async () => {
|
|
window.history.replaceState({}, '', '/group-rides?tab=radar&rideId=removed-ride');
|
|
render(<GroupRidesManagement currentUser={{ uid: 'member' }} />);
|
|
await act(async () => { await Promise.resolve(); });
|
|
act(() => streams.listeners.get('group_rides/removed-ride')?.error({ code: 'permission-denied', message: 'permission-denied' }));
|
|
await waitFor(() => expect(screen.getByTestId('telemetry-access-barrier')).toBeDefined());
|
|
expect(screen.queryByTestId('showcase-simulation-pill')).toBeNull();
|
|
});
|
|
});
|