fix(history): isolate query scope transitions

This commit is contained in:
lingniu
2026-07-16 04:36:41 +08:00
parent 6b4a0726b2
commit d95d7ab735
6 changed files with 131 additions and 10 deletions

View File

@@ -0,0 +1,78 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { afterEach, expect, test, vi } from 'vitest';
import { MemoryRouter } from 'react-router-dom';
import type { HistoryDataResponse, HistorySeriesResponse } from '../../api/types';
import { ROUTER_FUTURE } from '../routing/routerConfig';
import HistoryPage from './HistoryPage';
const mocks = vi.hoisted(() => ({
historyMetricCatalog: vi.fn(),
historyData: vi.fn(),
historySeries: vi.fn(),
historyExports: vi.fn(),
createHistoryExport: vi.fn()
}));
vi.mock('../../api/client', () => ({ api: mocks }));
afterEach(() => {
cleanup();
Object.values(mocks).forEach((mock) => mock.mockReset());
});
const metric = { key: 'speedKmh', label: '速度', unit: 'km/h', category: 'location', valueType: 'number', defaultVisible: true };
function historyData(vin: string, plate: string, asOf: string): HistoryDataResponse {
return {
category: 'location', columns: [metric], total: 1, limit: 50, offset: 0, asOf,
summary: { resultRows: 1, vehicleCount: 1, sources: ['JT808'], queryDurationMs: 12 },
rows: [{ id: `${vin}-row`, vin, plate, protocol: 'JT808', deviceTime: '2026-07-16 04:00:00', serverTime: '2026-07-16 04:00:01', quality: 'normal', evidenceId: `${vin}-evidence`, values: { speedKmh: 32 } }]
};
}
function historySeries(vin: string, plate: string, asOf: string): HistorySeriesResponse {
return {
metrics: [metric], dateFrom: '2026-07-16T00:00:00Z', dateTo: '2026-07-16T05:00:00Z', asOf,
summary: { rawPointCount: 2, bucketCount: 2, returnedPointCount: 2, seriesCount: 1, grainSeconds: 60, targetPoints: 240, expectedBucketCount: 2, missingBucketCount: 0, queryDurationMs: 8, complete: true, evidence: 'test series' },
series: [{ vin, plate, protocol: 'JT808', metric: 'speedKmh', label: '速度', unit: 'km/h', aggregation: 'avg', points: [{ time: '2026-07-16 04:00:00', value: 30, min: 30, max: 30, count: 1 }, { time: '2026-07-16 04:01:00', value: 32, min: 32, max: 32, count: 1 }] }]
};
}
function renderPage() {
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
return render(<QueryClientProvider client={client}><MemoryRouter future={ROUTER_FUTURE} initialEntries={['/history?keywords=OLDVIN&dateFrom=2026-07-16T00%3A00&dateTo=2026-07-16T05%3A00']}><HistoryPage /></MemoryRouter></QueryClientProvider>);
}
test('clears stale rows, charts, and evidence as soon as the history scope changes', async () => {
let resolveNewData!: (value: HistoryDataResponse) => void;
let resolveNewSeries!: (value: HistorySeriesResponse) => void;
mocks.historyMetricCatalog.mockResolvedValue({ categories: [{ key: 'location', label: '位置数据' }], metrics: [metric] });
mocks.historyExports.mockResolvedValue([]);
mocks.historyData.mockImplementation((params: URLSearchParams) => params.get('keywords') === 'OLDVIN'
? Promise.resolve(historyData('OLDVIN', '旧车牌', 'old-as-of'))
: new Promise<HistoryDataResponse>((resolve) => { resolveNewData = resolve; }));
mocks.historySeries.mockImplementation((params: URLSearchParams) => params.get('keywords') === 'OLDVIN'
? Promise.resolve(historySeries('OLDVIN', '旧车牌', 'old-as-of'))
: new Promise<HistorySeriesResponse>((resolve) => { resolveNewSeries = resolve; }));
renderPage();
expect((await screen.findAllByText('旧车牌')).length).toBeGreaterThan(0);
expect(await screen.findByText('OLDVIN-evidence')).toBeInTheDocument();
fireEvent.change(screen.getByPlaceholderText('车牌 / VIN多台用逗号分隔'), { target: { value: 'NEWVIN' } });
fireEvent.click(screen.getByRole('button', { name: '查询' }));
expect(await screen.findByRole('status')).toHaveTextContent('正在加载当前筛选范围的历史数据');
expect(screen.queryByText('旧车牌')).not.toBeInTheDocument();
expect(screen.queryByText('OLDVIN-evidence')).not.toBeInTheDocument();
expect(screen.getByText('正在聚合时间序列…')).toBeInTheDocument();
await act(async () => {
resolveNewData(historyData('NEWVIN', '新车牌', 'new-as-of'));
resolveNewSeries(historySeries('NEWVIN', '新车牌', 'new-as-of'));
});
expect((await screen.findAllByText('新车牌')).length).toBeGreaterThan(0);
expect(await screen.findByText('NEWVIN-evidence')).toBeInTheDocument();
await waitFor(() => expect(screen.queryByRole('status')).not.toBeInTheDocument());
});