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(); });