330 lines
22 KiB
TypeScript
330 lines
22 KiB
TypeScript
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||
import { act, cleanup, fireEvent, render, screen, waitFor, within } 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 { buildMonitorPath, withMonitorReturn } from '../routing/monitorContext';
|
||
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(),
|
||
downloadHistoryExport: vi.fn()
|
||
}));
|
||
const auth = vi.hoisted(() => ({ role: 'admin' }));
|
||
const layout = vi.hoisted(() => ({ mobile: false }));
|
||
|
||
vi.mock('../../api/client', () => ({ api: mocks }));
|
||
vi.mock('../auth/AuthGate', () => ({ usePlatformSession: () => ({ session: { role: auth.role } }) }));
|
||
vi.mock('../hooks/useMobileLayout', () => ({ useMobileLayout: () => layout.mobile }));
|
||
|
||
afterEach(() => {
|
||
cleanup();
|
||
auth.role = 'admin';
|
||
layout.mobile = false;
|
||
Object.values(mocks).forEach((mock) => mock.mockReset());
|
||
});
|
||
|
||
const metric = { key: 'speedKmh', label: '速度', unit: 'km/h', category: 'location', valueType: 'number', defaultVisible: true };
|
||
const optionalMetric = { key: 'socPercent', label: 'SOC', unit: '%', category: 'location', valueType: 'number', defaultVisible: false };
|
||
|
||
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', qualityReason: '坐标、速度、里程及设备/服务时间通过基础校验', 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(initialEntry = '/history?keywords=OLDVIN&dateFrom=2026-07-16T00%3A00&dateTo=2026-07-16T05%3A00') {
|
||
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||
const view = render(<QueryClientProvider client={client}><MemoryRouter future={ROUTER_FUTURE} initialEntries={[initialEntry]}><HistoryPage /></MemoryRouter></QueryClientProvider>);
|
||
return { ...view, client };
|
||
}
|
||
|
||
test('preserves the exact monitor return after changing history filters', async () => {
|
||
mocks.historyMetricCatalog.mockResolvedValue({ categories: [{ key: 'location', label: '位置数据' }], metrics: [metric] });
|
||
mocks.historyData.mockResolvedValue(historyData('OLDVIN', '旧车牌', 'old-as-of'));
|
||
mocks.historySeries.mockResolvedValue(historySeries('OLDVIN', '旧车牌', 'old-as-of'));
|
||
mocks.historyExports.mockResolvedValue([]);
|
||
const monitorPath = buildMonitorPath({
|
||
mode: 'list', keyword: '粤A12345', protocol: '', status: '', selectedVin: 'OLDVIN', detailOpen: false,
|
||
viewport: { zoom: 12.5, bounds: '' }, listOffset: 0, listLimit: 50, hasViewport: true
|
||
});
|
||
|
||
renderPage(withMonitorReturn('/history?keywords=OLDVIN&dateFrom=2026-07-16T00%3A00&dateTo=2026-07-16T05%3A00', monitorPath));
|
||
const returnLink = await screen.findByRole('link', { name: /返回全局监控/ });
|
||
expect(returnLink).toHaveAttribute('href', monitorPath);
|
||
|
||
fireEvent.change(screen.getByPlaceholderText('车牌 / VIN,多台用逗号分隔'), { target: { value: 'NEWVIN' } });
|
||
fireEvent.click(screen.getByRole('button', { name: '查询' }));
|
||
|
||
expect(await screen.findByRole('link', { name: /返回全局监控/ })).toHaveAttribute('href', monitorPath);
|
||
});
|
||
|
||
test('renders the shared empty state when the API returns nullable empty collections', async () => {
|
||
mocks.historyMetricCatalog.mockResolvedValue({ categories: [{ key: 'location', label: '位置数据' }], metrics: [metric] });
|
||
mocks.historyExports.mockResolvedValue([]);
|
||
mocks.historyData.mockResolvedValue({
|
||
category: 'location',
|
||
columns: null,
|
||
rows: null,
|
||
summary: null,
|
||
total: 0,
|
||
limit: 50,
|
||
offset: 0,
|
||
asOf: '2026-07-18 00:53:00'
|
||
} as unknown as HistoryDataResponse);
|
||
|
||
const view = renderPage('/history?keywords=LTESTNOEXIST00001&dateFrom=2026-07-18T00%3A00&dateTo=2026-07-18T00%3A53');
|
||
|
||
expect(await screen.findByText('当前条件没有历史记录')).toBeInTheDocument();
|
||
expect(view.container.querySelector('.v2-history-empty.v2-state-surface')).toBeInTheDocument();
|
||
expect(screen.queryByText('当前模块暂时无法显示')).not.toBeInTheDocument();
|
||
});
|
||
|
||
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; }));
|
||
|
||
const view = renderPage();
|
||
expect((await screen.findAllByText('旧车牌')).length).toBeGreaterThan(0);
|
||
expect(screen.queryByText('OLDVIN-evidence')).not.toBeInTheDocument();
|
||
for (const className of ['v2-history-filter-card', 'v2-history-context-card', 'v2-history-table-card']) {
|
||
expect(view.container.querySelector(`.${className}.semi-card`)).toBeInTheDocument();
|
||
}
|
||
expect(view.container.querySelector('.v2-history-context .v2-history-metrics')).toBeInTheDocument();
|
||
expect(view.container.querySelector('.v2-history-context .v2-history-summary')).toBeInTheDocument();
|
||
expect(view.container.querySelector('.v2-history-evidence')).not.toBeInTheDocument();
|
||
expect(view.container.querySelector('.v2-history-workspace')).not.toHaveClass('is-inspector-open');
|
||
expect(screen.queryByRole('heading', { level: 5, name: '聚合趋势' })).not.toBeInTheDocument();
|
||
expect(view.container.querySelector('.v2-history-trend')).not.toBeInTheDocument();
|
||
expect(screen.getByRole('button', { name: /展开趋势/ })).toHaveAttribute('aria-expanded', 'false');
|
||
expect(screen.queryByRole('heading', { level: 5, name: '导出任务' })).not.toBeInTheDocument();
|
||
expect(view.container.querySelector('.v2-history-data-table.semi-table-wrapper')).toBeInTheDocument();
|
||
expect(view.container.querySelector('.v2-history-table-scroll > table')).not.toBeInTheDocument();
|
||
expect(view.container.querySelector('.v2-history-mobile-list')).not.toBeInTheDocument();
|
||
const desktopHistoryRow = screen.getByTestId('history-row-OLDVIN-row');
|
||
expect(desktopHistoryRow).toHaveAttribute('role', 'button');
|
||
expect(desktopHistoryRow).toHaveAttribute('aria-expanded', 'false');
|
||
fireEvent.keyDown(desktopHistoryRow, { key: 'Enter' });
|
||
expect(desktopHistoryRow).toHaveAttribute('aria-expanded', 'true');
|
||
expect(await screen.findByText('OLDVIN-evidence')).toBeInTheDocument();
|
||
expect(view.container.querySelector('.v2-history-evidence-descriptions.semi-descriptions')).toBeInTheDocument();
|
||
expect(view.container.querySelector('.v2-evidence-values.semi-descriptions')).toBeInTheDocument();
|
||
expect(view.container.querySelector('.v2-history-quality-tag.semi-tag')).toHaveTextContent('正常');
|
||
expect(view.container.querySelector('.v2-history-workspace')).toHaveClass('is-inspector-open');
|
||
fireEvent.click(screen.getByRole('button', { name: '关闭数据详情' }));
|
||
expect(desktopHistoryRow).toHaveAttribute('aria-expanded', 'false');
|
||
expect(view.container.querySelector('.v2-history-workspace')).not.toHaveClass('is-inspector-open');
|
||
fireEvent.click(desktopHistoryRow);
|
||
expect(desktopHistoryRow).toHaveAttribute('aria-expanded', 'true');
|
||
fireEvent.click(screen.getByRole('button', { name: /展开趋势/ }));
|
||
await waitFor(() => expect(mocks.historySeries).toHaveBeenCalledTimes(1));
|
||
expect(screen.getByRole('heading', { level: 5, name: '聚合趋势' })).toBeInTheDocument();
|
||
expect(view.container.querySelector('.v2-history-trend')).toHaveClass('is-expanded');
|
||
|
||
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(screen.queryByText('NEWVIN-evidence')).not.toBeInTheDocument();
|
||
fireEvent.click(screen.getByTestId('history-row-NEWVIN-row'));
|
||
expect(await screen.findByText('NEWVIN-evidence')).toBeInTheDocument();
|
||
await waitFor(() => expect(screen.queryByRole('status')).not.toBeInTheDocument());
|
||
});
|
||
|
||
test('renders only selectable Semi history cards on mobile', async () => {
|
||
layout.mobile = true;
|
||
mocks.historyMetricCatalog.mockResolvedValue({ categories: [{ key: 'location', label: '位置数据' }], metrics: [metric] });
|
||
mocks.historyData.mockResolvedValue(historyData('MOBILEVIN', '粤A移动历史', 'mobile-as-of'));
|
||
mocks.historySeries.mockResolvedValue(historySeries('MOBILEVIN', '粤A移动历史', 'mobile-as-of'));
|
||
mocks.historyExports.mockResolvedValue([]);
|
||
const view = renderPage('/history?keywords=MOBILEVIN&dateFrom=2026-07-16T00%3A00&dateTo=2026-07-16T05%3A00');
|
||
|
||
const action = await screen.findByRole('button', { name: '查看 粤A移动历史 2026-07-16 04:00:00 数据详情' });
|
||
expect(action).toHaveClass('semi-button', 'v2-history-mobile-action');
|
||
expect(action.closest('.semi-card')).toHaveClass('v2-history-mobile-card');
|
||
expect(view.container.querySelector('.v2-history-data-table')).not.toBeInTheDocument();
|
||
expect(action).toHaveAttribute('aria-pressed', 'false');
|
||
expect(action).toHaveAttribute('aria-expanded', 'false');
|
||
expect(view.container.querySelector('.v2-history-table-scroll.is-mobile-scroll')).toHaveAttribute('aria-label', '历史明细列表,可上下滚动');
|
||
expect(view.container.querySelector('.v2-history-table-scroll.is-mobile-scroll')).toHaveAttribute('tabindex', '0');
|
||
expect(screen.queryByText('MOBILEVIN-evidence')).not.toBeInTheDocument();
|
||
expect((mocks.historyData.mock.calls[0]?.[0] as URLSearchParams).get('limit')).toBe('20');
|
||
expect(screen.queryByLabelText('每页数量')).not.toBeInTheDocument();
|
||
expect(screen.queryByLabelText('表格密度')).not.toBeInTheDocument();
|
||
expect(view.container.querySelector('.v2-history-side')).not.toBeInTheDocument();
|
||
expect(mocks.historyExports).not.toHaveBeenCalled();
|
||
fireEvent.click(action);
|
||
expect(action).toHaveAttribute('aria-pressed', 'true');
|
||
expect(action).toHaveAttribute('aria-expanded', 'true');
|
||
expect(await screen.findByRole('dialog', { name: '历史数据详情' })).toBeInTheDocument();
|
||
expect(await screen.findByText('MOBILEVIN-evidence')).toBeInTheDocument();
|
||
fireEvent.click(screen.getByRole('button', { name: '关闭历史数据详情' }));
|
||
expect(action).toHaveAttribute('aria-pressed', 'false');
|
||
expect(action).toHaveAttribute('aria-expanded', 'false');
|
||
expect(document.body.style.overflow).not.toBe('hidden');
|
||
expect(screen.queryByText('MOBILEVIN-evidence')).not.toBeInTheDocument();
|
||
|
||
const exportJobsButton = screen.getByRole('button', { name: '查看导出任务' });
|
||
fireEvent.click(exportJobsButton);
|
||
expect(await screen.findByRole('dialog', { name: '历史数据导出任务' })).toBeInTheDocument();
|
||
await waitFor(() => expect(mocks.historyExports).toHaveBeenCalledTimes(1));
|
||
expect(screen.getByText('暂无导出任务')).toBeInTheDocument();
|
||
fireEvent.click(screen.getByRole('button', { name: '关闭历史数据导出任务' }));
|
||
expect(exportJobsButton).toHaveAttribute('aria-expanded', 'false');
|
||
expect(document.body.style.overflow).not.toBe('hidden');
|
||
expect(screen.queryByText('暂无导出任务')).not.toBeInTheDocument();
|
||
});
|
||
|
||
test('releases export job state immediately after leaving the history page', async () => {
|
||
mocks.historyMetricCatalog.mockResolvedValue({ categories: [{ key: 'location', label: '位置数据' }], metrics: [metric] });
|
||
mocks.historyData.mockResolvedValue(historyData('OLDVIN', '旧车牌', 'old-as-of'));
|
||
mocks.historySeries.mockResolvedValue(historySeries('OLDVIN', '旧车牌', 'old-as-of'));
|
||
mocks.historyExports.mockResolvedValue([{ id: 'export-running', name: '运行中导出', status: 'running', progress: 30, format: 'csv', category: 'location', keywords: ['OLDVIN'], rowCount: 0, totalRows: 100, processedRows: 30, fileSizeBytes: 0, createdAt: '2026-07-16T04:00:00Z', updatedAt: '2026-07-16T04:00:01Z', evidence: 'test export' }]);
|
||
|
||
const { client, unmount } = renderPage();
|
||
fireEvent.click(await screen.findByRole('button', { name: '查看导出任务' }));
|
||
expect(await screen.findByText('运行中导出')).toBeInTheDocument();
|
||
expect(client.getQueryCache().find({ queryKey: ['history-exports'] })).toBeDefined();
|
||
|
||
unmount();
|
||
await waitFor(() => expect(client.getQueryCache().find({ queryKey: ['history-exports'] })).toBeUndefined());
|
||
});
|
||
|
||
test('opens a working column visibility panel and preserves non-hideable identity columns', async () => {
|
||
const data = historyData('OLDVIN', '旧车牌', 'old-as-of');
|
||
data.columns = [metric, optionalMetric];
|
||
data.rows[0].values.socPercent = 88;
|
||
mocks.historyMetricCatalog.mockResolvedValue({ categories: [{ key: 'location', label: '位置数据' }], metrics: [metric, optionalMetric] });
|
||
mocks.historyData.mockResolvedValue(data);
|
||
mocks.historySeries.mockResolvedValue(historySeries('OLDVIN', '旧车牌', 'old-as-of'));
|
||
mocks.historyExports.mockResolvedValue([]);
|
||
const view = renderPage();
|
||
|
||
expect(await screen.findByRole('columnheader', { name: '速度 (km/h)' })).toBeInTheDocument();
|
||
expect(screen.queryByRole('columnheader', { name: 'SOC (%)' })).not.toBeInTheDocument();
|
||
const metricScroll = view.container.querySelector('.v2-history-metric-scroll');
|
||
const manageMetrics = screen.getByRole('button', { name: '管理字段' });
|
||
expect(metricScroll).toBeInTheDocument();
|
||
expect(metricScroll).toHaveAttribute('aria-label', '当前显示字段');
|
||
expect(metricScroll?.contains(screen.getByLabelText(/已选字段 速度/))).toBe(true);
|
||
expect(metricScroll?.contains(manageMetrics)).toBe(false);
|
||
expect(manageMetrics.parentElement).toHaveClass('v2-history-metrics');
|
||
const columnSettingsButton = screen.getByRole('button', { name: '列设置' });
|
||
fireEvent.click(columnSettingsButton);
|
||
|
||
expect(screen.getByRole('dialog', { name: '列显示设置' })).toBeInTheDocument();
|
||
fireEvent.change(screen.getByPlaceholderText('搜索中文、状态含义或字段 key'), { target: { value: 'soc' } });
|
||
expect(screen.getByRole('checkbox', { name: /SOC/ })).toBeInTheDocument();
|
||
expect(screen.queryByRole('checkbox', { name: /速度/ })).not.toBeInTheDocument();
|
||
fireEvent.change(screen.getByPlaceholderText('搜索中文、状态含义或字段 key'), { target: { value: '' } });
|
||
fireEvent.click(screen.getByRole('checkbox', { name: /速度/ }));
|
||
expect(screen.queryByRole('columnheader', { name: '速度 (km/h)' })).not.toBeInTheDocument();
|
||
const historyTable = document.querySelector<HTMLElement>('.v2-history-table-scroll table');
|
||
expect(historyTable).toBeInTheDocument();
|
||
expect(within(historyTable!).getByRole('columnheader', { name: 'VIN' })).toBeInTheDocument();
|
||
|
||
fireEvent.click(screen.getByRole('button', { name: '显示全部' }));
|
||
expect(screen.getByRole('columnheader', { name: '速度 (km/h)' })).toBeInTheDocument();
|
||
expect(screen.getByRole('columnheader', { name: 'SOC (%)' })).toBeInTheDocument();
|
||
fireEvent.click(screen.getByRole('button', { name: '恢复默认' }));
|
||
expect(screen.getByRole('columnheader', { name: '速度 (km/h)' })).toBeInTheDocument();
|
||
expect(screen.queryByRole('columnheader', { name: 'SOC (%)' })).not.toBeInTheDocument();
|
||
fireEvent.click(screen.getByRole('button', { name: '关闭列显示设置' }));
|
||
expect(columnSettingsButton).toHaveAttribute('aria-expanded', 'false');
|
||
expect(document.body.style.overflow).not.toBe('hidden');
|
||
});
|
||
|
||
test('keeps history readable without mounting operator-only export requests for a viewer', async () => {
|
||
auth.role = 'viewer';
|
||
mocks.historyMetricCatalog.mockResolvedValue({ categories: [{ key: 'location', label: '位置数据' }], metrics: [metric] });
|
||
mocks.historyData.mockResolvedValue(historyData('OLDVIN', '旧车牌', 'old-as-of'));
|
||
mocks.historySeries.mockResolvedValue(historySeries('OLDVIN', '旧车牌', 'old-as-of'));
|
||
const view = renderPage();
|
||
|
||
expect((await screen.findAllByText('旧车牌')).length).toBeGreaterThan(0);
|
||
expect(screen.getByText('只读').closest('.semi-tag')).toHaveClass('v2-history-permission-tag');
|
||
expect(view.container.querySelector('.v2-history-toolbar-actions')).toBeInTheDocument();
|
||
expect(screen.queryByRole('button', { name: /创建导出/ })).not.toBeInTheDocument();
|
||
expect(screen.queryByText('导出任务')).not.toBeInTheDocument();
|
||
expect(mocks.historyExports).not.toHaveBeenCalled();
|
||
expect(mocks.createHistoryExport).not.toHaveBeenCalled();
|
||
});
|
||
|
||
test('shows only the authenticated customer export workspace with owner and scope metadata', async () => {
|
||
auth.role = 'customer';
|
||
mocks.historyMetricCatalog.mockResolvedValue({ categories: [{ key: 'location', label: '位置数据' }], metrics: [metric] });
|
||
mocks.historyData.mockResolvedValue(historyData('OLDVIN', '旧车牌', 'old-as-of'));
|
||
mocks.historySeries.mockResolvedValue(historySeries('OLDVIN', '旧车牌', 'old-as-of'));
|
||
mocks.historyExports.mockResolvedValue([{
|
||
id: 'customer-export', name: '历史数据_20260716_customer-a', status: 'completed', progress: 100, format: 'csv', category: 'location',
|
||
keywords: ['OLDVIN'], vehicleVins: ['OLDVIN'], dateFrom: '2026-07-16T00:00', dateTo: '2026-07-16T05:00',
|
||
ownerName: '客户甲', ownerUsername: 'customer-a', ownerRole: 'customer', ownerUserType: 'customer',
|
||
rowCount: 10, totalRows: 10, processedRows: 10, fileSizeBytes: 1024, downloadUrl: '/api/v2/exports/customer-export/download',
|
||
createdAt: '2026-07-16T05:00:00Z', updatedAt: '2026-07-16T05:00:01Z', completedAt: '2026-07-16T05:00:01Z', evidence: 'owner scoped'
|
||
}]);
|
||
renderPage();
|
||
|
||
fireEvent.click(await screen.findByRole('button', { name: '查看导出任务' }));
|
||
expect(await screen.findByText(/customer-a · 1 辆/)).toBeInTheDocument();
|
||
expect(screen.getByRole('dialog', { name: '历史数据导出任务' })).toBeInTheDocument();
|
||
expect(screen.getByRole('button', { name: /创建导出/ })).toBeInTheDocument();
|
||
expect(mocks.historyExports).toHaveBeenCalled();
|
||
});
|
||
|
||
test('uses selected chartable fields for trends and omits empty RAW evidence UI', async () => {
|
||
const totalMileageMetric = { key: 'totalMileageKm', label: '总里程', unit: 'km', category: 'location', valueType: 'number', defaultVisible: true };
|
||
const data = historyData('OLDVIN', '旧车牌', 'old-as-of');
|
||
data.columns = [metric, totalMileageMetric];
|
||
data.rows[0].evidenceId = undefined;
|
||
data.rows[0].values.totalMileageKm = 1234;
|
||
mocks.historyMetricCatalog.mockResolvedValue({ categories: [{ key: 'location', label: '位置数据' }], metrics: [metric, totalMileageMetric] });
|
||
mocks.historyData.mockResolvedValue(data);
|
||
mocks.historySeries.mockResolvedValue(historySeries('OLDVIN', '旧车牌', 'old-as-of'));
|
||
mocks.historyExports.mockResolvedValue([]);
|
||
renderPage();
|
||
|
||
expect((await screen.findAllByText('旧车牌')).length).toBeGreaterThan(0);
|
||
expect(mocks.historySeries).not.toHaveBeenCalled();
|
||
fireEvent.click(screen.getByRole('button', { name: /展开趋势/ }));
|
||
await waitFor(() => expect(mocks.historySeries).toHaveBeenCalled());
|
||
expect((mocks.historySeries.mock.calls[mocks.historySeries.mock.calls.length - 1]?.[0] as URLSearchParams).get('metrics')).toBe('speedKmh,totalMileageKm');
|
||
expect(screen.queryByText('RAW 证据')).not.toBeInTheDocument();
|
||
|
||
const mileageToggle = screen.getAllByLabelText(/已选字段 .*点击取消显示/).find((button) => button.textContent?.includes('总里程'));
|
||
expect(mileageToggle).toBeDefined();
|
||
fireEvent.click(mileageToggle!);
|
||
await waitFor(() => expect((mocks.historySeries.mock.calls[mocks.historySeries.mock.calls.length - 1]?.[0] as URLSearchParams).get('metrics')).toBe('speedKmh'));
|
||
});
|