feat(platform): harden telemetry pipeline and unify Semi UI workspaces

This commit is contained in:
lingniu
2026-07-18 00:26:36 +08:00
parent 65b4e4f055
commit 159c80b0ae
136 changed files with 21616 additions and 1785 deletions

View File

@@ -11,12 +11,15 @@ const mocks = vi.hoisted(() => ({
accessThresholds: vi.fn(), updateAccessThresholds: 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());
});
@@ -44,9 +47,23 @@ test('removes old access rows immediately when the vehicle filter scope changes'
? Promise.resolve({ items: [accessRow('OLDVIN', '旧接入车牌')], total: 1, limit: 50, offset: 0 })
: new Promise<Page<AccessVehicleRow>>((resolve) => { resolveNew = resolve; }));
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
render(<QueryClientProvider client={client}><MemoryRouter future={ROUTER_FUTURE} initialEntries={['/access?keyword=OLDVIN']}><AccessPage /></MemoryRouter></QueryClientProvider>);
const view = render(<QueryClientProvider client={client}><MemoryRouter future={ROUTER_FUTURE} initialEntries={['/access?keyword=OLDVIN']}><AccessPage /></MemoryRouter></QueryClientProvider>);
expect(await screen.findByText('旧接入车牌')).toBeInTheDocument();
for (const className of ['v2-access-filter-card-v3', 'v2-access-kpis-card-v3', 'v2-access-table-v3']) {
expect(view.container.querySelector(`.${className}.semi-card`)).toBeInTheDocument();
}
expect(view.container.querySelector('.v2-access-semi-table.semi-table-wrapper')).toBeInTheDocument();
expect(view.container.querySelector('.v2-access-table-scroll-v3 > table')).not.toBeInTheDocument();
const desktopRow = screen.getByTestId('access-row-OLDVIN');
expect(desktopRow).toHaveAttribute('role', 'button');
expect(desktopRow).toHaveAttribute('aria-expanded', 'false');
fireEvent.keyDown(desktopRow, { key: 'Enter' });
expect(await screen.findByRole('button', { name: '关闭车辆接入详情' })).toBeInTheDocument();
expect(desktopRow).toHaveAttribute('aria-expanded', 'true');
const inspectorHeader = screen.getByRole('heading', { level: 5, name: '旧接入车牌' }).closest<HTMLElement>('.v2-workspace-panel-header');
expect(inspectorHeader).toBeInTheDocument();
expect(within(inspectorHeader!).getByText('OLDVIN')).toBeInTheDocument();
fireEvent.change(screen.getByRole('textbox', { name: '车辆' }), { target: { value: 'NEWVIN' } });
fireEvent.click(screen.getByRole('button', { name: '查询' }));
@@ -66,13 +83,23 @@ test('reports and independently retries unresolved identity and threshold failur
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
render(<QueryClientProvider client={client}><MemoryRouter future={ROUTER_FUTURE} initialEntries={['/access']}><AccessPage /></MemoryRouter></QueryClientProvider>);
fireEvent.click(await screen.findByRole('button', { name: /接入治理/ }));
expect(await screen.findByRole('dialog', { name: '接入治理配置' })).toBeInTheDocument();
const identityError = await screen.findByText('待绑定身份服务超时');
const thresholdError = await screen.findByText('阈值配置读取失败');
fireEvent.click(within(identityError.closest('[role="alert"]')!).getByRole('button', { name: /重试/ }));
fireEvent.click(within(thresholdError.closest('[role="alert"]')!).getByRole('button', { name: /重试/ }));
expect(await screen.findByText('另有 1 条来源身份待绑定')).toBeInTheDocument();
expect(await screen.findByText('在线判定阈值 · v1')).toBeInTheDocument();
const identityTitle = await screen.findByText('来源身份待绑定');
const identityCollapse = identityTitle.closest<HTMLElement>('.semi-collapse');
expect(identityCollapse).toHaveClass('v2-access-identity-queue-v3');
expect(within(identityCollapse!).getByText('1 条').closest('.semi-tag')).toBeTruthy();
expect(within(identityCollapse!).getByText('138****0001').closest('.semi-card')).toHaveClass('v2-access-identity-card');
const thresholdTitle = await screen.findByText('在线判定阈值');
const thresholdCollapse = thresholdTitle.closest<HTMLElement>('.semi-collapse');
expect(thresholdCollapse).toHaveClass('v2-access-settings');
expect(within(thresholdCollapse!).getByText('v1').closest('.semi-tag')).toBeTruthy();
expect(screen.queryByText('待绑定身份服务超时')).not.toBeInTheDocument();
expect(screen.queryByText('阈值配置读取失败')).not.toBeInTheDocument();
expect(mocks.accessUnresolvedIdentities).toHaveBeenCalledTimes(2);
@@ -97,3 +124,26 @@ test('does not request or render admin-only thresholds for a read-only session',
expect(mocks.accessThresholds).not.toHaveBeenCalled();
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
});
test('renders mobile access vehicles as selectable Semi cards', async () => {
layout.mobile = true;
prepareBaseData();
mocks.accessVehicles.mockResolvedValue({ items: [accessRow('VIN001', '粤A00001')], total: 1, limit: 50, offset: 0 });
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
render(<QueryClientProvider client={client}><MemoryRouter future={ROUTER_FUTURE} initialEntries={['/access']}><AccessPage /></MemoryRouter></QueryClientProvider>);
const action = await screen.findByRole('button', { name: '查看 粤A00001 接入详情' });
expect(action).toHaveClass('semi-button', 'v2-access-mobile-action');
expect(action.closest('.semi-card')).toHaveClass('v2-access-mobile-card');
expect(action).toHaveAttribute('aria-pressed', 'false');
expect(action).toHaveAttribute('aria-expanded', 'false');
fireEvent.click(action);
expect(action).toHaveAttribute('aria-pressed', 'true');
expect(action).toHaveAttribute('aria-expanded', 'true');
const dialog = await screen.findByRole('dialog', { name: '车辆接入详情' });
expect(within(dialog).getByRole('button', { name: '关闭车辆接入详情' })).toBeInTheDocument();
expect(dialog.querySelector('.v2-access-inspector-v3.semi-card')).toBeInTheDocument();
expect(dialog.querySelector('.v2-access-inspector-summary.semi-descriptions')).toBeInTheDocument();
expect(dialog.querySelector('.v2-access-protocol-details.semi-card-group')).toBeInTheDocument();
expect(dialog.querySelectorAll('.v2-access-protocol-detail.semi-card')).toHaveLength(3);
});