初始化项目版本
This commit is contained in:
172
tests/vehicle-management/DetailModelParamsTab.test.tsx
Normal file
172
tests/vehicle-management/DetailModelParamsTab.test.tsx
Normal file
@@ -0,0 +1,172 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import React from 'react';
|
||||
import { afterEach, beforeAll, describe, expect, it } from 'vitest';
|
||||
import { DetailModelParamsTab } from '../../src/prototypes/vehicle-management/components/DetailModelParamsTab';
|
||||
import vehicles from '../../src/prototypes/vehicle-management/data/vehicles.json';
|
||||
import type { VehicleRecord } from '../../src/prototypes/vehicle-management/types';
|
||||
import { renderReact, type RenderReactResult } from './detailTestUtils';
|
||||
|
||||
let rendered: RenderReactResult | undefined;
|
||||
|
||||
function findClosingBrace(source: string, openingBraceIndex: number): number {
|
||||
let depth = 0;
|
||||
|
||||
for (let index = openingBraceIndex; index < source.length; index += 1) {
|
||||
if (source[index] === '{') {
|
||||
depth += 1;
|
||||
} else if (source[index] === '}') {
|
||||
depth -= 1;
|
||||
if (depth === 0) return index;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
(
|
||||
globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }
|
||||
).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await rendered?.unmount();
|
||||
rendered = undefined;
|
||||
});
|
||||
|
||||
describe('DetailModelParamsTab', () => {
|
||||
it('renders the model parameters as semantic readonly descriptions', async () => {
|
||||
rendered = await renderReact(
|
||||
<DetailModelParamsTab record={vehicles[0] as VehicleRecord} />,
|
||||
);
|
||||
const { container } = rendered;
|
||||
const tab = container.querySelector<HTMLElement>(
|
||||
'section[aria-label="型号参数(只读)"]',
|
||||
);
|
||||
const headings = Array.from(tab?.querySelectorAll('h3') ?? [], (heading) => (
|
||||
heading.textContent?.trim()
|
||||
));
|
||||
const grids = Array.from(tab?.querySelectorAll<HTMLElement>('.va-description-grid') ?? []);
|
||||
const items = Array.from(tab?.querySelectorAll<HTMLElement>('.va-description-item') ?? []);
|
||||
const labels = items.map((item) => item.querySelector('dt')?.textContent?.trim());
|
||||
|
||||
expect(tab).not.toBeNull();
|
||||
expect(tab?.tagName).toBe('SECTION');
|
||||
expect(headings).toEqual([
|
||||
'基本情况',
|
||||
'轮胎情况',
|
||||
'电气情况',
|
||||
'供氢系统情况',
|
||||
'其他系统情况',
|
||||
'保养规则',
|
||||
]);
|
||||
expect(grids.length).toBeGreaterThan(0);
|
||||
expect(grids.every((grid) => grid.tagName === 'DL')).toBe(true);
|
||||
expect(items.length).toBeGreaterThanOrEqual(10);
|
||||
expect(items.every((item) => (
|
||||
item.tagName === 'DIV'
|
||||
&& item.querySelector(':scope > dt') !== null
|
||||
&& item.querySelector(':scope > dd') !== null
|
||||
))).toBe(true);
|
||||
expect(labels).toEqual([
|
||||
'品牌',
|
||||
'型号',
|
||||
'车辆类型',
|
||||
'公告型号',
|
||||
'燃料种类',
|
||||
'车牌颜色',
|
||||
'车辆长度(米)',
|
||||
'车辆宽度(米)',
|
||||
'车辆高度(米)',
|
||||
'轮胎数量',
|
||||
'轮胎磨损费用(元/mm)',
|
||||
'电池类型',
|
||||
'电池厂家',
|
||||
'储电量',
|
||||
'电续航里程(KM)',
|
||||
'氢瓶容量',
|
||||
'仪表盘显示模式',
|
||||
'氢续航里程(KM)',
|
||||
'冷机厂家',
|
||||
]);
|
||||
expect(items[0].querySelector('dd')?.textContent).toBe('现代');
|
||||
expect(tab?.querySelector('.va-model-param-item__value')).toBeNull();
|
||||
expect(tab?.querySelector('input, select, textarea')).toBeNull();
|
||||
expect(tab?.querySelector('.va-model-param-table')?.textContent).toContain('保养公里周期');
|
||||
expect(tab?.querySelector('.va-model-param-table')?.textContent).toContain('变速器油');
|
||||
});
|
||||
|
||||
it('associates the maintenance table with its heading and identifies column headers', async () => {
|
||||
rendered = await renderReact(
|
||||
<DetailModelParamsTab record={vehicles[0] as VehicleRecord} />,
|
||||
);
|
||||
const heading = Array.from(rendered.container.querySelectorAll<HTMLHeadingElement>('h3'))
|
||||
.find((candidate) => candidate.textContent?.trim() === '保养规则');
|
||||
const table = rendered.container.querySelector<HTMLTableElement>('.va-model-param-table');
|
||||
const columnHeaders = Array.from(table?.querySelectorAll('th') ?? []);
|
||||
|
||||
expect(heading?.id).toBeTruthy();
|
||||
expect(table?.getAttribute('aria-labelledby')).toBe(heading?.id);
|
||||
expect(columnHeaders.length).toBeGreaterThan(0);
|
||||
expect(columnHeaders.every((header) => header.getAttribute('scope') === 'col')).toBe(true);
|
||||
});
|
||||
|
||||
it('renders empty parameter values as an em dash', async () => {
|
||||
rendered = await renderReact(
|
||||
<DetailModelParamsTab
|
||||
record={{ ...vehicles[0], brand: '-' } as VehicleRecord}
|
||||
/>,
|
||||
);
|
||||
const brandItem = Array.from(
|
||||
rendered.container.querySelectorAll<HTMLElement>('.va-description-item'),
|
||||
).find((item) => item.querySelector('dt')?.textContent?.trim() === '品牌');
|
||||
|
||||
expect(brandItem?.querySelector('dd')?.textContent).toBe('—');
|
||||
});
|
||||
|
||||
it('uses the page mobile breakpoint for the single-column description grid', () => {
|
||||
const descriptionGridSelector = '.va-model-param-tab .va-description-grid';
|
||||
const styles = readFileSync(
|
||||
resolve(process.cwd(), 'src/prototypes/vehicle-management/style.css'),
|
||||
'utf8',
|
||||
);
|
||||
const selectorIndexes: number[] = [];
|
||||
let selectorIndex = styles.indexOf(descriptionGridSelector);
|
||||
|
||||
while (selectorIndex >= 0) {
|
||||
selectorIndexes.push(selectorIndex);
|
||||
selectorIndex = styles.indexOf(descriptionGridSelector, selectorIndex + 1);
|
||||
}
|
||||
|
||||
expect(selectorIndexes).toHaveLength(2);
|
||||
|
||||
const mobileSelectorIndexes = selectorIndexes.filter((index) => {
|
||||
const ruleOpeningBrace = styles.indexOf('{', index);
|
||||
const ruleClosingBrace = findClosingBrace(styles, ruleOpeningBrace);
|
||||
const rule = styles.slice(index, ruleClosingBrace + 1);
|
||||
return rule.includes('grid-template-columns: minmax(0, 1fr)');
|
||||
});
|
||||
|
||||
expect(mobileSelectorIndexes).toHaveLength(1);
|
||||
|
||||
const mobileSelectorIndex = mobileSelectorIndexes[0];
|
||||
const nearestMediaIndex = styles.lastIndexOf('@media', mobileSelectorIndex);
|
||||
const nearestMediaBlockStart = styles.indexOf('{', nearestMediaIndex);
|
||||
const nearestMediaBlockEnd = findClosingBrace(styles, nearestMediaBlockStart);
|
||||
const nearestMediaHeader = styles
|
||||
.slice(nearestMediaIndex, nearestMediaBlockStart)
|
||||
.trim();
|
||||
const mobileRuleStart = styles.indexOf('{', mobileSelectorIndex);
|
||||
const mobileRuleEnd = findClosingBrace(styles, mobileRuleStart);
|
||||
const mobileRule = styles.slice(mobileSelectorIndex, mobileRuleEnd + 1);
|
||||
|
||||
expect(nearestMediaHeader).toBe('@media (max-width: 767px)');
|
||||
expect(mobileSelectorIndex).toBeGreaterThan(nearestMediaBlockStart);
|
||||
expect(mobileSelectorIndex).toBeLessThan(nearestMediaBlockEnd);
|
||||
expect(mobileRuleEnd).toBeLessThan(nearestMediaBlockEnd);
|
||||
expect(mobileRule).toContain('grid-template-columns: minmax(0, 1fr)');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user