fix(monitor): isolate batch search scope
This commit is contained in:
@@ -41,6 +41,45 @@ test('rapid viewport changes retain one workspace payload and avoid duplicate fl
|
||||
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 });
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { MAX_MONITOR_SEARCH_TERMS, MONITOR_CACHE, MONITOR_REFRESH, monitorMapQueryParams, monitorQueryParams, parseMonitorSearchTerms } from './useMonitorData';
|
||||
import { MAX_MONITOR_SEARCH_TERMS, MONITOR_CACHE, MONITOR_REFRESH, monitorFilterScope, monitorMapQueryParams, monitorQueryParams, parseMonitorSearchTerms } from './useMonitorData';
|
||||
|
||||
describe('monitor query params', () => {
|
||||
it('keeps server-owned status filters and bounded list size', () => {
|
||||
@@ -28,6 +28,15 @@ describe('monitor query params', () => {
|
||||
expect(params.get('keyword')).toBeNull();
|
||||
expect(parseMonitorSearchTerms(Array.from({ length: MAX_MONITOR_SEARCH_TERMS + 5 }, (_, index) => `粤A${index}`).join('\n'))).toHaveLength(MAX_MONITOR_SEARCH_TERMS);
|
||||
});
|
||||
|
||||
it('builds filter scope independently from viewport and result limit', () => {
|
||||
const scope = new URLSearchParams(monitorFilterScope({ keyword: '粤a12345\n粤B67890', protocol: 'JT808', status: 'online' }));
|
||||
expect(scope.get('keywords')).toBe('粤A12345,粤B67890');
|
||||
expect(scope.get('protocol')).toBe('JT808');
|
||||
expect(scope.get('status')).toBe('online');
|
||||
expect(scope.get('online')).toBe('online');
|
||||
expect(scope.has('limit')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('monitor refresh cadence', () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useQuery, type UseQueryResult } from '@tanstack/react-query';
|
||||
import { api } from '../../api/client';
|
||||
import type { MonitorMapResponse, MonitorSummary, MonitorWorkspaceResponse, Page, VehicleRealtimeRow } from '../../api/types';
|
||||
import { QUERY_MEMORY } from '../queryPolicy';
|
||||
import { QUERY_MEMORY, retainPreviousPageWithinScope } from '../queryPolicy';
|
||||
|
||||
export type MonitorFilters = {
|
||||
keyword: string;
|
||||
@@ -58,6 +58,12 @@ export function monitorMapQueryParams(filters: MonitorFilters, viewport: Monitor
|
||||
return params;
|
||||
}
|
||||
|
||||
export function monitorFilterScope(filters: MonitorFilters) {
|
||||
const params = monitorQueryParams(filters, 0);
|
||||
params.delete('limit');
|
||||
return params.toString();
|
||||
}
|
||||
|
||||
function projectWorkspaceQuery<T>(query: UseQueryResult<MonitorWorkspaceResponse>, select: (workspace: MonitorWorkspaceResponse) => T): UseQueryResult<T> {
|
||||
return { ...query, data: query.data ? select(query.data) : undefined } as UseQueryResult<T>;
|
||||
}
|
||||
@@ -65,13 +71,14 @@ function projectWorkspaceQuery<T>(query: UseQueryResult<MonitorWorkspaceResponse
|
||||
export function useMonitorData(filters: MonitorFilters, viewport: MonitorViewport, selectedVin: string, mapEnabled = true, vehicleEnabled = true) {
|
||||
const params = monitorQueryParams(filters, 200);
|
||||
const mapParams = monitorMapQueryParams(filters, viewport);
|
||||
const filterScope = monitorFilterScope(filters);
|
||||
mapParams.set('railLimit', '200');
|
||||
|
||||
const workspace = useQuery({
|
||||
queryKey: ['monitor', 'workspace', mapParams.toString()],
|
||||
const workspace = useQuery<MonitorWorkspaceResponse>({
|
||||
queryKey: ['monitor', 'workspace', filterScope, mapParams.toString()],
|
||||
queryFn: ({ signal }) => api.monitorWorkspace(mapParams, signal),
|
||||
enabled: mapEnabled,
|
||||
placeholderData: (previous) => previous,
|
||||
placeholderData: retainPreviousPageWithinScope<MonitorWorkspaceResponse>(filterScope, 2),
|
||||
staleTime: 5_000,
|
||||
gcTime: MONITOR_CACHE.mapGcTime,
|
||||
refetchInterval: mapEnabled ? MONITOR_REFRESH.fleet : false
|
||||
|
||||
Reference in New Issue
Block a user