perf(monitor): stop hidden selection polling

This commit is contained in:
lingniu
2026-07-16 05:34:19 +08:00
parent d669531128
commit 9c6cd09413
3 changed files with 36 additions and 7 deletions

View File

@@ -19,6 +19,7 @@ const vehicles = [{
const monitorMap = { clusters: [], points: [], total: 2 };
const fleetMapRenderSpy = vi.hoisted(() => vi.fn());
const vehicleCardArgsSpy = vi.hoisted(() => vi.fn());
const monitorDataArgsSpy = vi.hoisted(() => vi.fn());
const monitorQueryFlags = vi.hoisted(() => ({ isLoading: false, isFetching: false, isPlaceholderData: false }));
vi.mock('../map/FleetMap', () => ({
@@ -50,12 +51,15 @@ vi.mock('../hooks/useMonitorData', () => ({
if (filters.status) params.set('status', filters.status);
return params.toString();
},
useMonitorData: () => ({
summary: { data: { totalVehicles: 2, onlineVehicles: 2, offlineVehicles: 0, drivingVehicles: 1, idleVehicles: 1, frameToday: 10 } },
vehicles: { data: { items: vehicles, total: 2 }, isError: false, ...monitorQueryFlags },
map: { data: monitorMap, isPlaceholderData: monitorQueryFlags.isPlaceholderData },
selectedVehicle: { data: { items: [] } }
}),
useMonitorData: (...args: unknown[]) => {
monitorDataArgsSpy(...args);
return {
summary: { data: { totalVehicles: 2, onlineVehicles: 2, offlineVehicles: 0, drivingVehicles: 1, idleVehicles: 1, frameToday: 10 } },
vehicles: { data: { items: vehicles, total: 2 }, isError: false, ...monitorQueryFlags },
map: { data: monitorMap, isPlaceholderData: monitorQueryFlags.isPlaceholderData },
selectedVehicle: { data: { items: [] } }
};
},
useMonitorVehicleCard: (...args: unknown[]) => {
vehicleCardArgsSpy(...args);
return { detail: {}, activeAlerts: {}, address: {} };
@@ -68,6 +72,7 @@ afterEach(() => {
monitorQueryFlags.isFetching = false;
monitorQueryFlags.isPlaceholderData = false;
vehicleCardArgsSpy.mockClear();
monitorDataArgsSpy.mockClear();
vi.restoreAllMocks();
});
@@ -146,6 +151,21 @@ test('switches to a lightweight realtime list and resolves addresses only on dem
view.unmount();
});
test('pauses selected-vehicle polling in list mode and resumes it when returning to the map', () => {
vi.spyOn(api, 'vehicleRealtime').mockResolvedValue({ items: vehicles, total: 2, limit: 50, offset: 0 });
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE}><MonitorPage /></MemoryRouter></QueryClientProvider>);
fireEvent.click(screen.getByRole('button', { name: /粤A12345 LTEST000000000001/ }));
expect(monitorDataArgsSpy).toHaveBeenLastCalledWith(expect.any(Object), expect.any(Object), 'LTEST000000000001', true, true);
fireEvent.click(screen.getByRole('button', { name: /列表/ }));
expect(monitorDataArgsSpy).toHaveBeenLastCalledWith(expect.any(Object), expect.any(Object), '', false, false);
fireEvent.click(screen.getByRole('button', { name: /地图/ }));
expect(monitorDataArgsSpy).toHaveBeenLastCalledWith(expect.any(Object), expect.any(Object), 'LTEST000000000001', true, true);
});
test('mounts only the mobile list representation and removes its viewport listener', async () => {
const addEventListener = vi.fn();
const removeEventListener = vi.fn();

View File

@@ -219,7 +219,8 @@ export default function MonitorPage() {
params.set('offset', String(listOffset));
return params;
}, [filters, listLimit, listOffset]);
const { summary, vehicles, map, selectedVehicle } = useMonitorData(filters, viewport, selectedVin, mode === 'map', mode === 'map');
const trackedVin = mode === 'map' ? selectedVin : '';
const { summary, vehicles, map, selectedVehicle } = useMonitorData(filters, viewport, trackedVin, mode === 'map', mode === 'map');
const realtimeListQuery = useQuery<Page<VehicleRealtimeRow>>({
queryKey: ['monitor', 'vehicle-list', listScope, listLimit, listOffset],
queryFn: ({ signal }) => api.vehicleRealtime(listParams, signal),

View File

@@ -2,6 +2,14 @@
This document records verified risks, the production controls that address them, and the next evidence to collect. It is intentionally operational: a passing build alone is not proof that the browser application is production-ready.
## 2026-07-16: visible-scope selected-vehicle polling
Selecting a vehicle starts a ten-second realtime query so the map can keep its ripple, plate and camera target synchronized. Switching to the full fleet list unmounted the map and detail card but retained the selected VIN, so that invisible query continued polling up to 360 times per hour per open operator tab without any rendered consumer.
The monitor now retains the operator's selected VIN as UI state but passes it to the realtime query only while map mode is active. Entering list mode changes the query key to the disabled empty selection, which stops the interval and lets the prior high-volume result reach its zero-retention cleanup; returning to the map restores the same selection and resumes live tracking. TanStack Query documents that an `enabled: false` query does not automatically fetch or refetch in the background, while `refetchInterval` otherwise operates independently of `staleTime`: <https://tanstack.com/query/latest/docs/framework/react/guides/disabling-queries>, <https://tanstack.com/query/latest/docs/framework/react/guides/important-defaults>.
A component wiring test selects a vehicle, proves live tracking receives its VIN, switches to list mode and proves the polling input becomes empty, then returns to the map and proves the original selection resumes. Existing cache lifecycle coverage continues proving replaced selected-vehicle payloads are removed immediately.
## 2026-07-16: bounded route-module recovery
The route shell already displayed a meaningful Suspense skeleton and recovered rejected lazy chunks, but a dynamic import that remained pending forever never reached the error boundary. Operators could therefore stay on a loading workspace indefinitely during a stalled resource request. React documents that `lazy` caches its Promise and that Suspense continues showing the nearest fallback until that Promise settles; a rejected Promise is then delivered to the nearest error boundary: <https://react.dev/reference/react/lazy>, <https://react.dev/reference/react/Suspense>.