Files
lingniu-vehicle-ingest/vehicle-data-platform/apps/web/src/v2/pages/MonitorVehicleTable.test.tsx
T
lingniu d6b957dfc8 feat: sync vehicle monitoring and accumulated platform updates
Add monitoring metrics, numeric sorting, report-time display, vehicle number labels, and map layout fixes. Include accumulated mileage, hydrogen, OneOS authentication, open platform, ingestion, deployment, and UI updates.

Exclude temporary files, generated reports, and credentials. Validate frontend 632 tests and production build, API packages, vehicle-map tests, and release installer tests. Gateway suite has one pre-existing date-sensitive identity throttling test failure reproduced on the parent commit.
2026-10-26 18:05:59 +08:00

116 lines
7.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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(<Harness props={{ ...props, rows: [{ ...vehicle, vehicleNumber: '01Q' }] }} />);
expect(screen.getByLabelText('车辆编号 01Q')).toHaveTextContent(/^01Q$/);
expect(screen.getByTitle('车辆编号:01Q')).toBeVisible();
view.rerender(<Harness props={{ ...props, rows: [{ ...vehicle, vehicleNumber: '备用车110Q' }] }} />);
expect(screen.queryByLabelText('车辆编号 01Q')).not.toBeInTheDocument();
expect(screen.getByLabelText('车辆编号 备用车110Q')).toBeInTheDocument();
view.rerender(<Harness props={{ ...props, rows: [{ ...vehicle, vehicleNumber: undefined }] }} />);
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<typeof MonitorVehicleTable> {
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<typeof MonitorVehicleTable> }) {
return <QueryClientProvider client={new QueryClient()}><MemoryRouter future={ROUTER_FUTURE}><MonitorVehicleTable {...props} /></MemoryRouter></QueryClientProvider>;
}
test('shows reported SOC and hydrogen values with units in the desktop list', () => {
render(<Harness props={tableProps(false)} />);
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(<Harness props={props} />);
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(<Harness props={{ ...props, rows: [{ ...vehicle, socPercent: 0, hydrogenPressureMpa: 0, hydrogenMassKg: 0, hydrogenConsumptionKgPer100Km: 0 }] }} />);
expect(energyCells().map((cell) => cell.textContent)).toEqual(['0', '0', '0 Kg', '0']);
view.rerender(<Harness props={{ ...props, rows: [{ ...vehicle, socAvailable: false, hydrogenPressureMpa: undefined, hydrogenMassKg: null, hydrogenConsumptionKgPer100Km: Number.NaN }] }} />);
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(<Harness props={{ ...props, rows: [{ ...vehicle, hydrogenMassSource: 'ESTIMATED' }] }} />);
expect(screen.getByText('Kg · 估算')).toBeInTheDocument();
expect(screen.getByTitle('根据氢气压力、温度和该车气瓶总容积估算')).toHaveTextContent('14.32');
view.rerender(<Harness props={{ ...props, rows: [{ ...vehicle, hydrogenMassSource: 'REPORTED' }] }} />);
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(<Harness props={{ ...props, rows: [{ ...vehicle, lastSeen: '2026-10-26 14:59:30' }] }} />);
expect(screen.getByTitle('2026-10-26 14:59:30')).toHaveTextContent('实时');
expect(screen.getByTitle('2026-10-26 14:59:30')).toHaveClass('is-realtime');
view.rerender(<Harness props={{ ...props, rows: [{ ...vehicle, lastSeen: '2026-10-26 14:00:30' }] }} />);
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(<Harness props={{ ...tableProps(mobile), rows: [{ ...vehicle, lastSeen: '2026-10-26T06:59:30Z' }] }} />);
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(<Harness props={{ ...tableProps(mobile), rows: [{ ...vehicle, lastSeen: '' }] }} />);
expect(screen.queryByRole('button', { name: /查看上报时间/ })).not.toBeInTheDocument();
});