178 lines
11 KiB
TypeScript
178 lines
11 KiB
TypeScript
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
import { act, cleanup, render, renderHook, waitFor } from '@testing-library/react';
|
|
import { afterEach, expect, test, vi } from 'vitest';
|
|
import type { ReactNode } from 'react';
|
|
import { api } from '../../api/client';
|
|
import { useMonitorData, useMonitorVehicleCard } from './useMonitorData';
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
vi.useRealTimers();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
test('rapid viewport changes retain one workspace payload and avoid duplicate fleet requests', async () => {
|
|
const monitorSummary = vi.spyOn(api, 'monitorSummary').mockResolvedValue({ totalVehicles: 0, onlineVehicles: 0, offlineVehicles: 0, drivingVehicles: 0, idleVehicles: 0, unknownVehicles: 0, alertVehicles: 0, activeToday: 0, frameToday: 0, alertDataAvailable: false, truncated: false, asOf: '' });
|
|
const vehicleRealtime = vi.spyOn(api, 'vehicleRealtime').mockResolvedValue({ items: [], total: 0, limit: 200, offset: 0 });
|
|
const monitorWorkspace = vi.spyOn(api, 'monitorWorkspace').mockResolvedValue({
|
|
summary: { totalVehicles: 0, onlineVehicles: 0, offlineVehicles: 0, drivingVehicles: 0, idleVehicles: 0, unknownVehicles: 0, alertVehicles: 0, activeToday: 0, frameToday: 0, alertDataAvailable: false, truncated: false, asOf: '' },
|
|
vehicles: { items: [], total: 0, limit: 200, offset: 0 },
|
|
map: { mode: 'points', zoom: 13, total: 0, truncated: false, points: [], clusters: [], asOf: '' }
|
|
});
|
|
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
|
const wrapper = ({ children }: { children: ReactNode }) => <QueryClientProvider client={client}>{children}</QueryClientProvider>;
|
|
const filters = { keyword: '', protocol: '', status: '' };
|
|
const view = renderHook(({ bounds }) => useMonitorData(filters, { zoom: 13, bounds }, '', true, false), {
|
|
wrapper,
|
|
initialProps: { bounds: '113.0000,22.0000,114.0000,23.0000' }
|
|
});
|
|
|
|
await waitFor(() => expect(monitorWorkspace).toHaveBeenCalledTimes(1));
|
|
const workspaceQuery = client.getQueryCache().findAll({ queryKey: ['monitor', 'workspace'] })[0];
|
|
const liveOptions = workspaceQuery?.options as { refetchIntervalInBackground?: boolean; refetchOnWindowFocus?: boolean };
|
|
expect(liveOptions.refetchIntervalInBackground).toBe(false);
|
|
expect(liveOptions.refetchOnWindowFocus).toBe(true);
|
|
expect(monitorSummary).not.toHaveBeenCalled();
|
|
expect(vehicleRealtime).not.toHaveBeenCalled();
|
|
for (let index = 1; index <= 8; index += 1) {
|
|
view.rerender({ bounds: `${113 + index / 1000},22,${114 + index / 1000},23` });
|
|
await waitFor(() => expect(monitorWorkspace.mock.calls.length).toBeGreaterThanOrEqual(index + 1));
|
|
}
|
|
await waitFor(() => {
|
|
const cachedWorkspaces = client.getQueryCache().findAll({ queryKey: ['monitor', 'workspace'] });
|
|
expect(cachedWorkspaces).toHaveLength(1);
|
|
});
|
|
view.unmount();
|
|
client.clear();
|
|
});
|
|
|
|
test('retains workspace data for viewport changes but clears it across filter scopes', async () => {
|
|
const oldWorkspace = {
|
|
summary: { totalVehicles: 1, onlineVehicles: 1, offlineVehicles: 0, drivingVehicles: 1, idleVehicles: 0, unknownVehicles: 0, alertVehicles: 0, activeToday: 1, frameToday: 1, alertDataAvailable: false, truncated: false, asOf: '' },
|
|
vehicles: { items: [{ vin: 'OLDVIN', plate: '旧车牌' }], total: 1, limit: 200, offset: 0 },
|
|
map: { mode: 'points', zoom: 13, total: 1, truncated: false, points: [], clusters: [], asOf: '' }
|
|
} as never;
|
|
let calls = 0;
|
|
const monitorWorkspace = vi.spyOn(api, 'monitorWorkspace').mockImplementation(() => {
|
|
calls += 1;
|
|
return calls === 1 ? Promise.resolve(oldWorkspace) : new Promise(() => undefined);
|
|
});
|
|
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
|
const wrapper = ({ children }: { children: ReactNode }) => <QueryClientProvider client={client}>{children}</QueryClientProvider>;
|
|
const view = renderHook(({ bounds, protocol }) => useMonitorData(
|
|
{ keyword: '', protocol, status: '' },
|
|
{ zoom: 13, bounds },
|
|
'',
|
|
true,
|
|
false
|
|
), {
|
|
wrapper,
|
|
initialProps: { bounds: '113,22,114,23', protocol: '' }
|
|
});
|
|
|
|
await waitFor(() => expect(view.result.current.vehicles.data?.items[0]?.vin).toBe('OLDVIN'));
|
|
view.rerender({ bounds: '113.1,22,114.1,23', protocol: '' });
|
|
await waitFor(() => expect(monitorWorkspace).toHaveBeenCalledTimes(2));
|
|
expect(view.result.current.vehicles.data?.items[0]?.vin).toBe('OLDVIN');
|
|
expect(view.result.current.vehicles.isPlaceholderData).toBe(true);
|
|
|
|
view.rerender({ bounds: '113.1,22,114.1,23', protocol: 'JT808' });
|
|
await waitFor(() => expect(monitorWorkspace).toHaveBeenCalledTimes(3));
|
|
expect(view.result.current.vehicles.data).toBeUndefined();
|
|
expect(view.result.current.vehicles.isLoading).toBe(true);
|
|
|
|
view.unmount();
|
|
client.clear();
|
|
});
|
|
|
|
test('repeated vehicle selections retain only the active realtime payload and release it on unmount', async () => {
|
|
vi.spyOn(api, 'monitorSummary').mockResolvedValue({ totalVehicles: 0, onlineVehicles: 0, offlineVehicles: 0, drivingVehicles: 0, idleVehicles: 0, unknownVehicles: 0, alertVehicles: 0, activeToday: 0, frameToday: 0, alertDataAvailable: false, truncated: false, asOf: '' });
|
|
const vehicleRealtime = vi.spyOn(api, 'vehicleRealtime').mockResolvedValue({ items: [], total: 0, limit: 1, offset: 0 });
|
|
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
|
const wrapper = ({ children }: { children: ReactNode }) => <QueryClientProvider client={client}>{children}</QueryClientProvider>;
|
|
const filters = { keyword: '', protocol: '', status: '' };
|
|
const view = renderHook(({ vin }) => useMonitorData(filters, { zoom: 5, bounds: '' }, vin, false, false), {
|
|
wrapper,
|
|
initialProps: { vin: 'VIN-001' }
|
|
});
|
|
|
|
await waitFor(() => expect(vehicleRealtime).toHaveBeenCalledTimes(1));
|
|
for (const vin of ['VIN-002', 'VIN-003', 'VIN-004']) {
|
|
view.rerender({ vin });
|
|
await waitFor(() => expect(vehicleRealtime.mock.calls.length).toBeGreaterThanOrEqual(Number(vin.slice(-1))));
|
|
}
|
|
await waitFor(() => expect(client.getQueryCache().findAll({ queryKey: ['monitor', 'selected-vehicle'] })).toHaveLength(1));
|
|
view.unmount();
|
|
await waitFor(() => expect(client.getQueryCache().findAll({ queryKey: ['monitor', 'selected-vehicle'] })).toHaveLength(0));
|
|
client.clear();
|
|
});
|
|
|
|
test('collapsed vehicle card does not issue or retain detail-only queries', async () => {
|
|
const detail = vi.spyOn(api, 'vehicleDetail').mockResolvedValue({} as never);
|
|
const alerts = vi.spyOn(api, 'alertEventsV2').mockResolvedValue({ items: [], total: 0, limit: 20, offset: 0 });
|
|
const address = vi.spyOn(api, 'reverseGeocode').mockResolvedValue({ provider: 'AMap', longitude: 113.26, latitude: 23.13, formattedAddress: '测试地址' });
|
|
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
|
const vehicle = { vin: 'VIN-001', longitude: 113.26, latitude: 23.13 } as never;
|
|
function ActiveCard() {
|
|
useMonitorVehicleCard('VIN-001', vehicle, true);
|
|
return null;
|
|
}
|
|
const view = render(<QueryClientProvider client={client}>{null}</QueryClientProvider>);
|
|
|
|
expect(detail).not.toHaveBeenCalled();
|
|
expect(alerts).not.toHaveBeenCalled();
|
|
expect(address).not.toHaveBeenCalled();
|
|
view.rerender(<QueryClientProvider client={client}><ActiveCard /></QueryClientProvider>);
|
|
await waitFor(() => expect(detail).toHaveBeenCalledTimes(1));
|
|
await waitFor(() => expect(alerts).toHaveBeenCalledTimes(1));
|
|
await waitFor(() => expect(address).toHaveBeenCalledTimes(1));
|
|
view.rerender(<QueryClientProvider client={client}>{null}</QueryClientProvider>);
|
|
await waitFor(() => expect(client.getQueryCache().findAll({ queryKey: ['monitor', 'vehicle-card'] })).toHaveLength(0));
|
|
view.unmount();
|
|
client.clear();
|
|
});
|
|
|
|
test('throttles moving vehicle address lookups, publishes the latest point, and switches vehicles immediately', async () => {
|
|
vi.useFakeTimers();
|
|
vi.spyOn(api, 'vehicleDetail').mockResolvedValue({} as never);
|
|
vi.spyOn(api, 'alertEventsV2').mockResolvedValue({ items: [], total: 0, limit: 20, offset: 0 });
|
|
const address = vi.spyOn(api, 'reverseGeocode').mockResolvedValue({ provider: 'AMap', longitude: 113.26, latitude: 23.13, formattedAddress: '测试地址' });
|
|
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
|
function ActiveCard({ vehicle }: { vehicle: { vin: string; longitude: number; latitude: number } }) {
|
|
useMonitorVehicleCard(vehicle.vin, vehicle as never, true);
|
|
return null;
|
|
}
|
|
const view = render(<QueryClientProvider client={client}><ActiveCard vehicle={{ vin: 'VIN-001', longitude: 113.260041, latitude: 23.130041 }} /></QueryClientProvider>);
|
|
|
|
await act(async () => { await Promise.resolve(); await Promise.resolve(); });
|
|
expect(address).toHaveBeenCalledTimes(1);
|
|
expect(address.mock.calls[0]?.[0]?.get('longitude')).toBe('113.2600');
|
|
expect(address.mock.calls[0]?.[0]?.get('latitude')).toBe('23.1300');
|
|
|
|
view.rerender(<QueryClientProvider client={client}><ActiveCard vehicle={{ vin: 'VIN-001', longitude: 113.260049, latitude: 23.130049 }} /></QueryClientProvider>);
|
|
await act(async () => { await Promise.resolve(); });
|
|
expect(address).toHaveBeenCalledTimes(1);
|
|
|
|
view.rerender(<QueryClientProvider client={client}><ActiveCard vehicle={{ vin: 'VIN-001', longitude: 113.270041, latitude: 23.140041 }} /></QueryClientProvider>);
|
|
await act(async () => { vi.advanceTimersByTime(30_000); await Promise.resolve(); });
|
|
view.rerender(<QueryClientProvider client={client}><ActiveCard vehicle={{ vin: 'VIN-001', longitude: 113.280041, latitude: 23.150041 }} /></QueryClientProvider>);
|
|
await act(async () => { vi.advanceTimersByTime(29_999); await Promise.resolve(); });
|
|
expect(address).toHaveBeenCalledTimes(1);
|
|
|
|
await act(async () => { vi.advanceTimersByTime(1); await Promise.resolve(); await Promise.resolve(); });
|
|
expect(address).toHaveBeenCalledTimes(2);
|
|
expect(address.mock.calls[1]?.[0]?.get('longitude')).toBe('113.2800');
|
|
expect(address.mock.calls[1]?.[0]?.get('latitude')).toBe('23.1500');
|
|
|
|
view.rerender(<QueryClientProvider client={client}><ActiveCard vehicle={{ vin: 'VIN-002', longitude: 114.310041, latitude: 22.910041 }} /></QueryClientProvider>);
|
|
await act(async () => { await Promise.resolve(); await Promise.resolve(); });
|
|
expect(address).toHaveBeenCalledTimes(3);
|
|
expect(address.mock.calls[2]?.[0]?.get('longitude')).toBe('114.3100');
|
|
|
|
view.rerender(<QueryClientProvider client={client}><ActiveCard vehicle={{ vin: 'VIN-002', longitude: 114.320041, latitude: 22.920041 }} /></QueryClientProvider>);
|
|
view.unmount();
|
|
await act(async () => { vi.advanceTimersByTime(60_000); await Promise.resolve(); });
|
|
expect(address).toHaveBeenCalledTimes(3);
|
|
client.clear();
|
|
});
|