381 lines
22 KiB
TypeScript
381 lines
22 KiB
TypeScript
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||
import { afterEach, expect, test, vi } from 'vitest';
|
||
import { MemoryRouter } from 'react-router-dom';
|
||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||
import type { VehicleRealtimeRow } from '../../api/types';
|
||
import { api } from '../../api/client';
|
||
import MonitorPage from './MonitorPage';
|
||
import { ROUTER_FUTURE } from '../routing/routerConfig';
|
||
|
||
const vehicles = [{
|
||
vin: 'LTEST000000000001', plate: '粤A12345', protocols: ['JT808'], primaryProtocol: 'JT808',
|
||
locationAvailable: true, longitude: 113.26, latitude: 23.13, speedAvailable: true, speedKmh: 42,
|
||
socAvailable: false, socPercent: 0, mileageAvailable: true, totalMileageKm: 1234, todayMileageAvailable: true, todayMileageKm: 18.6,
|
||
lastSeen: '2026-07-14T01:00:00Z', online: true, sourceCount: 1, onlineSourceCount: 1
|
||
}, {
|
||
vin: 'LTEST000000000002', plate: '粤B67890', protocols: ['JT808'], primaryProtocol: 'JT808',
|
||
locationAvailable: true, longitude: 113.28, latitude: 23.15, speedAvailable: true, speedKmh: 0,
|
||
socAvailable: false, socPercent: 0, mileageAvailable: true, totalMileageKm: 2234, todayMileageAvailable: false, todayMileageKm: 0,
|
||
lastSeen: '2026-07-14T01:00:00Z', online: true, sourceCount: 1, onlineSourceCount: 1
|
||
}] as VehicleRealtimeRow[];
|
||
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 qrToDataURLSpy = vi.hoisted(() => vi.fn());
|
||
const monitorQueryFlags = vi.hoisted(() => ({ isLoading: false, isFetching: false, isPlaceholderData: false }));
|
||
const originalClipboard = Object.getOwnPropertyDescriptor(navigator, 'clipboard');
|
||
|
||
vi.mock('../map/FleetMap', () => ({
|
||
FleetMap: ({ selectedVin, onSelectVin, initialViewport }: { selectedVin?: string; onSelectVin?: (vin: string) => void; initialViewport?: { zoom: number; bounds: string } }) => {
|
||
fleetMapRenderSpy(selectedVin, initialViewport);
|
||
return <div data-testid="fleet-map" data-selected-vin={selectedVin ?? ''} data-initial-zoom={initialViewport?.zoom ?? ''} data-initial-bounds={initialViewport?.bounds ?? ''}>
|
||
<button type="button" onClick={() => onSelectVin?.('LTEST000000000002')}>选择地图车辆</button>
|
||
</div>;
|
||
}
|
||
}));
|
||
|
||
vi.mock('qrcode', () => ({ default: { toDataURL: qrToDataURLSpy } }));
|
||
|
||
vi.mock('../hooks/useMonitorData', () => ({
|
||
MAX_MONITOR_SEARCH_TERMS: 100,
|
||
MONITOR_REFRESH: { selected: 10_000, fleet: 15_000, summary: 30_000 },
|
||
parseMonitorSearchTerms: (value: string) => Array.from(new Set(value.split(/[\s,,、;;]+/).map((item) => item.trim().toLocaleUpperCase()).filter(Boolean))).slice(0, 100),
|
||
monitorQueryParams: (filters: { keyword: string; protocol: string; status: string }, limit: number) => {
|
||
const params = new URLSearchParams({ limit: String(limit) });
|
||
const terms = Array.from(new Set(filters.keyword.split(/[\s,,、;;]+/).map((item) => item.trim().toLocaleUpperCase()).filter(Boolean))).slice(0, 100);
|
||
if (terms.length === 1) params.set('keyword', terms[0]);
|
||
if (terms.length > 1) params.set('keywords', terms.join(','));
|
||
if (filters.protocol) params.set('protocol', filters.protocol);
|
||
if (filters.status) params.set('status', filters.status);
|
||
return params;
|
||
},
|
||
monitorFilterScope: (filters: { keyword: string; protocol: string; status: string }) => {
|
||
const params = new URLSearchParams();
|
||
if (filters.keyword) params.set('keyword', filters.keyword);
|
||
if (filters.protocol) params.set('protocol', filters.protocol);
|
||
if (filters.status) params.set('status', filters.status);
|
||
return params.toString();
|
||
},
|
||
useMonitorData: (...args: unknown[]) => {
|
||
monitorDataArgsSpy(...args);
|
||
return {
|
||
summary: { data: { totalVehicles: 2, locationVehicles: 2, noLocationVehicles: 0, 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: {} };
|
||
}
|
||
}));
|
||
|
||
afterEach(() => {
|
||
cleanup();
|
||
monitorQueryFlags.isLoading = false;
|
||
monitorQueryFlags.isFetching = false;
|
||
monitorQueryFlags.isPlaceholderData = false;
|
||
vehicleCardArgsSpy.mockClear();
|
||
monitorDataArgsSpy.mockClear();
|
||
qrToDataURLSpy.mockReset();
|
||
if (originalClipboard) Object.defineProperty(navigator, 'clipboard', originalClipboard);
|
||
else Reflect.deleteProperty(navigator, 'clipboard');
|
||
vi.restoreAllMocks();
|
||
});
|
||
|
||
test('starts without a selection and supports expand, collapse, reselection, and clear', () => {
|
||
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||
const view = render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE}><MonitorPage /></MemoryRouter></QueryClientProvider>);
|
||
const workspace = view.container.querySelector('.v2-monitor-workspace')!;
|
||
const firstVehicle = screen.getByRole('button', { name: /粤A12345 LTEST000000000001/ });
|
||
const secondVehicle = screen.getByRole('button', { name: /粤B67890 LTEST000000000002/ });
|
||
|
||
expect(workspace).not.toHaveClass('is-detail-open');
|
||
expect(workspace).not.toHaveClass('is-detail-collapsed');
|
||
expect(firstVehicle).not.toHaveClass('is-selected');
|
||
expect(screen.queryByRole('button', { name: '取消选择车辆' })).not.toBeInTheDocument();
|
||
expect(screen.getByTestId('fleet-map')).toHaveAttribute('data-selected-vin', '');
|
||
expect(screen.getByText('车辆总数')).toBeInTheDocument();
|
||
expect(screen.getAllByText('无实时位置').length).toBeGreaterThanOrEqual(2);
|
||
expect(screen.getByRole('status', { name: '搜索和筛选条件修改后自动生效' })).toHaveTextContent('实时筛选');
|
||
expect(screen.queryByRole('button', { name: '筛选' })).not.toBeInTheDocument();
|
||
expect(screen.queryByText('接入车辆')).not.toBeInTheDocument();
|
||
|
||
fireEvent.click(firstVehicle);
|
||
expect(workspace).toHaveClass('is-detail-open');
|
||
expect(firstVehicle).toHaveClass('is-selected');
|
||
expect(screen.getByRole('button', { name: '取消选择车辆' })).toBeInTheDocument();
|
||
expect(screen.getByTestId('fleet-map')).toHaveAttribute('data-selected-vin', 'LTEST000000000001');
|
||
expect(vehicleCardArgsSpy).toHaveBeenLastCalledWith('LTEST000000000001', vehicles[0], true);
|
||
expect(screen.getByText('SOC').parentElement).toHaveTextContent('SOC—');
|
||
const mapRendersAfterSelection = fleetMapRenderSpy.mock.calls.length;
|
||
const cardCallsAfterSelection = vehicleCardArgsSpy.mock.calls.length;
|
||
|
||
fireEvent.click(screen.getByRole('button', { name: '收起车辆详情' }));
|
||
expect(workspace).toHaveClass('is-detail-collapsed');
|
||
expect(firstVehicle).toHaveClass('is-selected');
|
||
expect(screen.getByRole('button', { name: '展开车辆详情' })).toBeInTheDocument();
|
||
expect(fleetMapRenderSpy).toHaveBeenCalledTimes(mapRendersAfterSelection);
|
||
expect(vehicleCardArgsSpy).toHaveBeenCalledTimes(cardCallsAfterSelection);
|
||
|
||
fireEvent.click(secondVehicle);
|
||
expect(workspace).toHaveClass('is-detail-open');
|
||
expect(secondVehicle).toHaveClass('is-selected');
|
||
expect(screen.queryByRole('button', { name: '展开车辆详情' })).not.toBeInTheDocument();
|
||
|
||
fireEvent.click(screen.getByRole('button', { name: '取消选择车辆' }));
|
||
expect(workspace).not.toHaveClass('is-detail-open');
|
||
expect(workspace).not.toHaveClass('is-detail-collapsed');
|
||
expect(secondVehicle).not.toHaveClass('is-selected');
|
||
expect(screen.getByTestId('fleet-map')).toHaveAttribute('data-selected-vin', '');
|
||
|
||
fireEvent.click(screen.getByRole('button', { name: '选择地图车辆' }));
|
||
expect(workspace).toHaveClass('is-detail-open');
|
||
expect(secondVehicle).toHaveClass('is-selected');
|
||
});
|
||
|
||
test('restores URL-backed monitor context and carries it into every vehicle workflow', () => {
|
||
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||
const entry = '/monitor?keyword=%E7%B2%A4A12345&protocol=JT808&status=online&selectedVin=LTEST000000000001&detail=collapsed&zoom=13&bounds=113.100000%2C23.000000%2C113.400000%2C23.300000';
|
||
render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE} initialEntries={[entry]}><MonitorPage /></MemoryRouter></QueryClientProvider>);
|
||
|
||
expect(screen.getByRole('textbox', { name: '搜索车辆' })).toHaveValue('粤A12345');
|
||
expect(screen.getByRole('combobox', { name: '协议' })).toHaveValue('JT808');
|
||
expect(screen.getByRole('combobox', { name: '在线状态' })).toHaveValue('online');
|
||
expect(screen.getByRole('button', { name: '展开车辆详情' })).toBeInTheDocument();
|
||
expect(screen.getByTestId('fleet-map')).toHaveAttribute('data-initial-zoom', '13');
|
||
expect(screen.getByTestId('fleet-map')).toHaveAttribute('data-initial-bounds', '113.100000,23.000000,113.400000,23.300000');
|
||
|
||
fireEvent.click(screen.getByRole('button', { name: '展开车辆详情' }));
|
||
for (const name of ['单车详情', '轨迹回放', '历史数据', '里程查询']) {
|
||
const target = new URL(screen.getByRole('link', { name }).getAttribute('href')!, 'https://vehicle-platform.invalid');
|
||
const monitorReturn = target.searchParams.get('monitorReturn');
|
||
expect(monitorReturn).toContain('/monitor?');
|
||
expect(monitorReturn).toContain('selectedVin=LTEST000000000001');
|
||
expect(monitorReturn).toContain('zoom=13');
|
||
}
|
||
});
|
||
|
||
test('switches to a lightweight realtime list and resolves addresses only on demand', async () => {
|
||
const row = vehicles[0];
|
||
vi.spyOn(api, 'vehicleRealtime').mockResolvedValue({ items: [row], total: 1, limit: 50, offset: 0 });
|
||
const reverseGeocode = vi.spyOn(api, 'reverseGeocode').mockResolvedValue({ provider: 'AMap', longitude: 113.26, latitude: 23.13, formattedAddress: '广东省广州市天河区测试路' });
|
||
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||
const view = render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE}><MonitorPage /></MemoryRouter></QueryClientProvider>);
|
||
fireEvent.click(screen.getByRole('button', { name: /列表/ }));
|
||
expect(await screen.findByText('车辆实时列表')).toBeInTheDocument();
|
||
expect(screen.getByText('覆盖全部授权车辆;实时字段缺失时显示“—”,文字地址按需解析')).toBeInTheDocument();
|
||
expect(await screen.findAllByText('粤A12345')).toHaveLength(1);
|
||
expect(screen.getAllByText('42')).toHaveLength(1);
|
||
expect(screen.getAllByText(/1,234/)).toHaveLength(1);
|
||
expect(screen.getAllByText(/18\.6/)).toHaveLength(1);
|
||
expect(screen.getAllByText(/113\.260000/)).toHaveLength(1);
|
||
expect(reverseGeocode).not.toHaveBeenCalled();
|
||
|
||
fireEvent.click(screen.getByRole('button', { name: '解析粤A12345位置' }));
|
||
expect(await screen.findByText('广东省广州市天河区测试路')).toBeInTheDocument();
|
||
expect(screen.getByText(/解析于 \d{2}:\d{2}/)).toBeInTheDocument();
|
||
expect(reverseGeocode).toHaveBeenCalledTimes(1);
|
||
expect(queryClient.getQueryCache().findAll({ queryKey: ['monitor', 'list-address'] })).toHaveLength(1);
|
||
expect(screen.queryByText('综合状态')).not.toBeInTheDocument();
|
||
expect(screen.queryByText('三路正常')).not.toBeInTheDocument();
|
||
expect(screen.queryByTestId('fleet-map')).not.toBeInTheDocument();
|
||
|
||
fireEvent.click(screen.getByRole('button', { name: /地图/ }));
|
||
await waitFor(() => expect(queryClient.getQueryCache().findAll({ queryKey: ['monitor', 'list-address'] })).toHaveLength(0));
|
||
view.unmount();
|
||
});
|
||
|
||
test('keeps authorized vehicles without realtime coordinates in the list without geocoding them', async () => {
|
||
const row = {
|
||
...vehicles[0],
|
||
vin: 'LNOLOCATION0000001',
|
||
plate: '粤A无定位',
|
||
protocols: [],
|
||
primaryProtocol: '',
|
||
locationAvailable: false,
|
||
longitude: 0,
|
||
latitude: 0,
|
||
speedAvailable: false,
|
||
speedKmh: 0,
|
||
mileageAvailable: false,
|
||
totalMileageKm: 0,
|
||
todayMileageAvailable: false,
|
||
todayMileageKm: 0,
|
||
lastSeen: '',
|
||
online: false,
|
||
sourceCount: 0,
|
||
onlineSourceCount: 0
|
||
} satisfies VehicleRealtimeRow;
|
||
vi.spyOn(api, 'vehicleRealtime').mockResolvedValue({ items: [row], total: 1, limit: 50, offset: 0 });
|
||
const reverseGeocode = vi.spyOn(api, 'reverseGeocode');
|
||
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: /列表/ }));
|
||
|
||
expect(await screen.findByText('粤A无定位')).toBeInTheDocument();
|
||
expect(screen.getByText('暂无实时位置')).toBeInTheDocument();
|
||
expect(screen.getByText('从未上报')).toBeInTheDocument();
|
||
expect(screen.queryByRole('button', { name: /解析粤A无定位位置/ })).not.toBeInTheDocument();
|
||
expect(reverseGeocode).not.toHaveBeenCalled();
|
||
});
|
||
|
||
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('loads the QR generator only after opening the mobile entry', async () => {
|
||
qrToDataURLSpy.mockResolvedValue('data:image/png;base64,vehicle-monitor');
|
||
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||
render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE}><MonitorPage /></MemoryRouter></QueryClientProvider>);
|
||
|
||
expect(qrToDataURLSpy).not.toHaveBeenCalled();
|
||
fireEvent.click(screen.getByRole('button', { name: /手机端/ }));
|
||
|
||
expect(await screen.findByRole('img', { name: '全局监控手机端二维码' })).toHaveAttribute('src', 'data:image/png;base64,vehicle-monitor');
|
||
expect(qrToDataURLSpy).toHaveBeenCalledTimes(1);
|
||
expect(qrToDataURLSpy.mock.calls[0][0]).toMatch(/\/monitor$/);
|
||
expect(qrToDataURLSpy.mock.calls[0][0]).not.toContain('token');
|
||
});
|
||
|
||
test('closes the mobile entry with Escape and reports clipboard success', async () => {
|
||
qrToDataURLSpy.mockResolvedValue('data:image/png;base64,vehicle-monitor');
|
||
const writeText = vi.fn().mockResolvedValue(undefined);
|
||
Object.defineProperty(navigator, 'clipboard', { configurable: true, value: { writeText } });
|
||
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: /手机端/ }));
|
||
expect(await screen.findByRole('img', { name: '全局监控手机端二维码' })).toBeInTheDocument();
|
||
fireEvent.click(screen.getByRole('button', { name: '复制访问地址' }));
|
||
expect(await screen.findByRole('button', { name: '已复制访问地址' })).toBeInTheDocument();
|
||
expect(writeText).toHaveBeenCalledWith(expect.stringMatching(/\/monitor$/));
|
||
|
||
fireEvent.keyDown(window, { key: 'Escape' });
|
||
expect(screen.queryByRole('dialog', { name: '手机端入口' })).not.toBeInTheDocument();
|
||
});
|
||
|
||
test('shows a retry action when on-demand QR generation fails', async () => {
|
||
qrToDataURLSpy.mockRejectedValueOnce(new Error('canvas unavailable')).mockResolvedValue('data:image/png;base64,recovered');
|
||
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: /手机端/ }));
|
||
expect(await screen.findByRole('alert')).toHaveTextContent('二维码生成失败');
|
||
fireEvent.click(screen.getByRole('button', { name: '重新生成' }));
|
||
|
||
expect(await screen.findByRole('img', { name: '全局监控手机端二维码' })).toHaveAttribute('src', 'data:image/png;base64,recovered');
|
||
expect(qrToDataURLSpy).toHaveBeenCalledTimes(2);
|
||
});
|
||
|
||
test('mounts only the mobile list representation and removes its viewport listener', async () => {
|
||
const addEventListener = vi.fn();
|
||
const removeEventListener = vi.fn();
|
||
vi.spyOn(window, 'matchMedia').mockReturnValue({
|
||
matches: true,
|
||
media: '(max-width: 760px)',
|
||
onchange: null,
|
||
addEventListener,
|
||
removeEventListener,
|
||
addListener: vi.fn(),
|
||
removeListener: vi.fn(),
|
||
dispatchEvent: vi.fn(() => false)
|
||
} as unknown as MediaQueryList);
|
||
vi.spyOn(api, 'vehicleRealtime').mockResolvedValue({ items: [vehicles[0]], total: 1, limit: 50, offset: 0 });
|
||
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||
const view = render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE}><MonitorPage /></MemoryRouter></QueryClientProvider>);
|
||
|
||
fireEvent.click(screen.getByRole('button', { name: /列表/ }));
|
||
expect(await screen.findByText('车辆实时列表')).toBeInTheDocument();
|
||
await waitFor(() => expect(view.container.querySelectorAll('.v2-monitor-mobile-cards article')).toHaveLength(1));
|
||
expect(view.container.querySelector('.v2-monitor-table-scroll')).not.toBeInTheDocument();
|
||
expect(addEventListener).toHaveBeenCalledWith('change', expect.any(Function));
|
||
|
||
view.unmount();
|
||
expect(removeEventListener).toHaveBeenCalledWith('change', expect.any(Function));
|
||
});
|
||
|
||
test('pastes, deduplicates, and submits multiple plates as one batch search', async () => {
|
||
const vehicleRealtime = 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>);
|
||
|
||
const input = screen.getByRole('textbox', { name: '搜索车辆' });
|
||
fireEvent.paste(input, { clipboardData: { getData: () => '粤a12345\n粤B67890,粤A12345' } });
|
||
expect(input).toHaveValue('粤A12345,粤B67890');
|
||
expect(screen.getAllByText('已找到 2/2')).toHaveLength(3);
|
||
|
||
fireEvent.click(screen.getByRole('button', { name: /列表/ }));
|
||
await waitFor(() => {
|
||
const params = vehicleRealtime.mock.calls[vehicleRealtime.mock.calls.length - 1]?.[0];
|
||
expect(params?.get('keywords')).toBe('粤A12345,粤B67890');
|
||
expect(params?.has('keyword')).toBe(false);
|
||
});
|
||
});
|
||
|
||
test('opens an explicit batch editor and applies copied plate rows', async () => {
|
||
const vehicleRealtime = 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: '批量' }));
|
||
const editor = screen.getByRole('textbox', { name: /每行一个车牌/ });
|
||
fireEvent.change(editor, { target: { value: '粤a12345\n粤B67890\n粤A12345' } });
|
||
expect(screen.getByText('2', { selector: '.v2-batch-search-summary strong' })).toBeInTheDocument();
|
||
fireEvent.click(screen.getByRole('button', { name: '应用搜索(2)' }));
|
||
|
||
expect(screen.queryByRole('dialog', { name: '批量搜索车辆' })).not.toBeInTheDocument();
|
||
expect(screen.getByRole('textbox', { name: '搜索车辆' })).toHaveValue('粤A12345,粤B67890');
|
||
fireEvent.click(screen.getByRole('button', { name: /列表/ }));
|
||
await waitFor(() => {
|
||
const params = vehicleRealtime.mock.calls[vehicleRealtime.mock.calls.length - 1]?.[0];
|
||
expect(params?.get('keywords')).toBe('粤A12345,粤B67890');
|
||
});
|
||
});
|
||
|
||
test('hides stale fleet rows while a pasted batch scope is still loading', () => {
|
||
monitorQueryFlags.isLoading = true;
|
||
monitorQueryFlags.isPlaceholderData = true;
|
||
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||
render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE}><MonitorPage /></MemoryRouter></QueryClientProvider>);
|
||
|
||
fireEvent.paste(screen.getByRole('textbox', { name: '搜索车辆' }), { clipboardData: { getData: () => '粤A12345\n粤B67890' } });
|
||
expect(screen.getByText('正在查找 2 辆车')).toBeInTheDocument();
|
||
expect(screen.getByText('查询中')).toBeInTheDocument();
|
||
expect(screen.queryByRole('button', { name: /粤A12345 LTEST000000000001/ })).not.toBeInTheDocument();
|
||
});
|
||
|
||
test('keeps same-filter fleet rows visible while a viewport placeholder refreshes', () => {
|
||
monitorQueryFlags.isPlaceholderData = true;
|
||
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||
render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE}><MonitorPage /></MemoryRouter></QueryClientProvider>);
|
||
|
||
expect(screen.getByRole('button', { name: /粤A12345 LTEST000000000001/ })).toBeInTheDocument();
|
||
expect(screen.queryByText('加载车辆')).not.toBeInTheDocument();
|
||
expect(screen.getByTestId('fleet-map')).toBeInTheDocument();
|
||
});
|
||
|
||
test('reports pasted plates that have no exact realtime match', () => {
|
||
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||
render(<QueryClientProvider client={queryClient}><MemoryRouter future={ROUTER_FUTURE}><MonitorPage /></MemoryRouter></QueryClientProvider>);
|
||
|
||
fireEvent.paste(screen.getByRole('textbox', { name: '搜索车辆' }), { clipboardData: { getData: () => '粤A12345\n粤Z99999' } });
|
||
expect(screen.getByText('已找到 1/2')).toBeInTheDocument();
|
||
expect(screen.getAllByText('已找到 1/2 · 未找到 1 辆')).toHaveLength(2);
|
||
expect(screen.getAllByTitle('未找到:粤Z99999')).toHaveLength(2);
|
||
});
|