import { act, cleanup, fireEvent, render, screen, within } 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 { ComponentProps } from 'react';
import type { VehicleRealtimeRow } from '../../api/types';
import MonitorVehicleTable from './MonitorVehicleTable';
import { ROUTER_FUTURE } from '../routing/routerConfig';
afterEach(cleanup);
test.each([false, true])('shows only configured vehicle number labels and updates them (mobile=%s)', (mobile) => {
const props = tableProps(mobile);
const view = render();
expect(screen.getByLabelText('车辆编号 01Q')).toHaveTextContent(/^01Q$/);
expect(screen.getByTitle('车辆编号:01Q')).toBeVisible();
view.rerender();
expect(screen.queryByLabelText('车辆编号 01Q')).not.toBeInTheDocument();
expect(screen.getByLabelText('车辆编号 备用车110Q')).toBeInTheDocument();
view.rerender();
expect(view.container.querySelector('.v2-monitor-vehicle-number')).toBeNull();
});
const vehicle = {
vin: 'LTEST000000000001', plate: '粤A12345', protocols: ['GB32960'], primaryProtocol: 'GB32960',
locationAvailable: false, speedAvailable: false, mileageAvailable: false,
socAvailable: true, socPercent: 65.4, hydrogenPressureMpa: 32.5, hydrogenMassKg: 14.32,
hydrogenConsumptionKgPer100Km: 8.76
} as VehicleRealtimeRow;
function tableProps(mobile: boolean): ComponentProps {
return {
rows: [vehicle], total: 1, page: 1, totalPages: 1, limit: 50, loading: false, error: false,
sort: 'identity', sortOrder: 'asc', onSort: vi.fn(), mobile, selectedVin: '', mobileScrollTop: 0, onMobileScroll: vi.fn(), onSelect: vi.fn(), onPage: vi.fn(), onLimit: vi.fn()
};
}
function Harness({ props }: { props: ComponentProps }) {
return ;
}
test('shows reported SOC and hydrogen values with units in the desktop list', () => {
render();
for (const name of ['上报时间', '电 SOC(%)', '氢气压力(MPa)', '剩余氢量(Kg)', '百公里氢耗(kg/100km)']) {
expect(screen.getByRole('columnheader', { name })).toBeInTheDocument();
}
for (const value of ['65.4', '32.5', '14.32', '8.76']) expect(screen.getByText(value)).toBeInTheDocument();
expect(screen.getAllByRole('columnheader')).toHaveLength(14);
const remainingCell = within(screen.getAllByRole('row')[1]).getAllByRole('cell')[6];
expect(remainingCell).toHaveTextContent('32.5');
expect(within(screen.getAllByRole('row')[1]).getAllByRole('cell')[7]).toHaveTextContent('14.32 Kg');
expect(screen.queryByRole('button', { name: '剩余氢量(Kg)' })).not.toBeInTheDocument();
});
test.each([false, true])('distinguishes missing data from zero and refreshes energy values (mobile=%s)', (mobile) => {
const props = tableProps(mobile);
const view = render();
const energyCells = () => mobile
? Array.from(view.container.querySelectorAll('.v2-monitor-mobile-energy dd'))
: within(screen.getAllByRole('row')[1]).getAllByRole('cell').slice(5, 9);
expect(energyCells().map((cell) => cell.textContent)).toEqual(['65.4', '32.5', '14.32 Kg', '8.76']);
view.rerender();
expect(energyCells().map((cell) => cell.textContent)).toEqual(['0', '0', '0 Kg', '0']);
view.rerender();
expect(energyCells().map((cell) => cell.textContent)).toEqual(['—', '—', '— Kg', '—']);
});
test.each([false, true])('labels pressure-derived mass as estimated (mobile=%s)', (mobile) => {
const props = tableProps(mobile);
const view = render();
expect(screen.getByText('Kg · 估算')).toBeInTheDocument();
expect(screen.getByTitle('根据氢气压力、温度和该车气瓶总容积估算')).toHaveTextContent('14.32');
view.rerender();
expect(screen.queryByText('Kg · 估算')).not.toBeInTheDocument();
});
test.each([false, true])('refreshes report time and exact hover timestamp (mobile=%s)', (mobile) => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-10-26T15:00:00+08:00'));
try {
const props = tableProps(mobile);
const view = render();
expect(screen.getByTitle('2026-10-26 14:59:30')).toHaveTextContent('实时');
expect(screen.getByTitle('2026-10-26 14:59:30')).toHaveClass('is-realtime');
view.rerender();
expect(screen.getByTitle('2026-10-26 14:00:30')).toHaveTextContent('实时');
act(() => vi.advanceTimersByTime(60_000));
expect(screen.getByTitle('2026-10-26 14:00:30')).toHaveTextContent('1小时前');
expect(screen.getByTitle('2026-10-26 14:00:30')).toHaveClass('is-recent');
expect(screen.getByTitle('2026-10-26 14:00:30')).toHaveAttribute('datetime', '2026-10-26T06:00:30.000Z');
view.unmount();
} finally {
vi.useRealTimers();
}
});
test.each([false, true])('opens exact report time on click and closes with Escape (mobile=%s)', async (mobile) => {
const view = render();
const trigger = screen.getByRole('button', { name: '查看上报时间:2026-10-26 14:59:30' });
fireEvent.click(trigger);
const detail = await screen.findByRole('dialog', { name: '上报时间详情' });
expect(within(detail).getByText('2026-10-26 14:59:30')).toBeVisible();
expect(within(detail).getByText('上报时间(北京时间)')).toBeVisible();
fireEvent.keyDown(document, { key: 'Escape' });
expect(screen.queryByRole('dialog', { name: '上报时间详情' })).not.toBeInTheDocument();
fireEvent.click(trigger);
fireEvent.click(screen.getByRole('button', { name: '关闭上报时间详情' }));
expect(screen.queryByRole('dialog', { name: '上报时间详情' })).not.toBeInTheDocument();
view.rerender();
expect(screen.queryByRole('button', { name: /查看上报时间/ })).not.toBeInTheDocument();
});