feat(vehicle): live-update single vehicle map

This commit is contained in:
lingniu
2026-07-16 10:20:18 +08:00
parent e0e7e28762
commit 59f220885b
2 changed files with 84 additions and 4 deletions

View File

@@ -0,0 +1,69 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { cleanup, render, waitFor } from '@testing-library/react';
import { afterEach, expect, test, vi } from 'vitest';
import { MemoryRouter, Route, Routes } from 'react-router-dom';
import { api } from '../../api/client';
import type { VehicleDetail, VehicleRealtimeRow } from '../../api/types';
import { ROUTER_FUTURE } from '../routing/routerConfig';
import VehiclePage from './VehiclePage';
const fleetMapVehicles = vi.hoisted(() => vi.fn());
vi.mock('../map/FleetMap', () => ({
FleetMap: ({ vehicles }: { vehicles: VehicleRealtimeRow[] }) => {
fleetMapVehicles(vehicles);
return <div data-testid="fleet-map" />;
}
}));
vi.mock('../auth/AuthGate', () => ({
usePlatformSession: () => ({ session: { name: 'test-viewer', role: 'viewer', authMode: 'enforce' } })
}));
const initialRealtime = {
vin: 'LTEST000000000001', plate: '粤A12345', phone: '', oem: '', protocols: ['JT808'], sourceStatus: [],
sourceCount: 1, onlineSourceCount: 1, online: true, bindingStatus: 'bound', primaryProtocol: 'JT808',
longitude: 113.26, latitude: 23.13, speedKmh: 20, socPercent: 80, totalMileageKm: 1000,
lastSeen: '2026-07-16 10:00:00', reportIntervalMs: 30_000
} satisfies VehicleRealtimeRow;
const detail = {
vin: initialRealtime.vin,
lookupKey: initialRealtime.vin,
lookupResolved: true,
realtimeSummary: initialRealtime,
sources: ['JT808'],
sourceStatus: [],
realtime: [],
history: { items: [], total: 0, limit: 20, offset: 0 },
raw: { items: [], total: 0, limit: 20, offset: 0 },
mileage: { items: [], total: 0, limit: 20, offset: 0 },
quality: { items: [], total: 0, limit: 20, offset: 0 }
} satisfies VehicleDetail;
afterEach(() => {
cleanup();
vi.restoreAllMocks();
fleetMapVehicles.mockReset();
});
test('polls lightweight single-vehicle realtime data and passes its report interval to the map', async () => {
const movedRealtime = { ...initialRealtime, longitude: 113.28, latitude: 23.15, lastSeen: '2026-07-16 10:00:30' };
vi.spyOn(api, 'vehicleDetail').mockResolvedValue(detail);
const realtimeSpy = vi.spyOn(api, 'vehicleRealtime').mockResolvedValue({ items: [movedRealtime], total: 1, limit: 1, offset: 0 });
vi.spyOn(api, 'latestTelemetry').mockResolvedValue({ values: [], categories: [], scannedFrames: 0 } as never);
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
render(<QueryClientProvider client={client}><MemoryRouter initialEntries={[`/vehicles/${initialRealtime.vin}`]} future={ROUTER_FUTURE}><Routes><Route path="/vehicles/:vin" element={<VehiclePage />} /></Routes></MemoryRouter></QueryClientProvider>);
await waitFor(() => expect(realtimeSpy).toHaveBeenCalledTimes(1));
const realtimeQuery = client.getQueryCache().find({ queryKey: ['vehicle-detail-realtime', initialRealtime.vin] });
expect((realtimeQuery?.options as { refetchInterval?: number }).refetchInterval).toBe(10_000);
const params = realtimeSpy.mock.calls[0][0];
expect(params).toBeDefined();
expect(params!.get('keywords')).toBe(initialRealtime.vin);
expect(params!.get('limit')).toBe('1');
await waitFor(() => expect(fleetMapVehicles).toHaveBeenLastCalledWith([
expect.objectContaining({ longitude: 113.28, latitude: 23.15, reportIntervalMs: 30_000 })
]));
});