测试
两层:单元测试跑在 node 里(快、无浏览器),浏览器 QA 跑真实页面(慢、但能查外观与交互)。两层测试的职责划分与覆盖:
一、单元测试(npm test)
jest + node 环境(没有 jsdom)。所以组件测试的套路是:假 ICE + 真组件。
const handlers: Record<string, Array<{ handler: any; ctx: any }>> = {};
const ice: any = {
canvasWidth: 800, canvasHeight: 600, toolNodes: [], dirty: false,
evtBus: {
on(name, handler, ctx) { (handlers[name] = handlers[name] || []).push({ handler, ctx }); },
off(name, handler) { handlers[name] = (handlers[name] || []).filter((e) => e.handler !== handler); },
trigger(name, evt) { (handlers[name] || []).forEach((e) => e.handler.call(e.ctx, evt)); },
},
addTool(tool) { ice.toolNodes.push(tool); },
removeTool(tool) { /* … */ },
setFocusedComponent() {}, getFocusedComponent: () => null,
screenToWorld: (x, y) => [x, y],
getRenderViewport: () => ({ scale: 1, tx: 0, ty: 0 }),
};
const picker = new ICEDatePicker({ /* … */ });
(picker as any).ice = ice; // 直接挂上假 ICE
(picker as any).afterAddHandler(); // 手动触发“加入场景”钩子,注册全局事件
覆盖范围(107 个 suite / 825 条):
| 主题 | 例子 |
|---|---|
| 表单与校验 | 规则求值、异步校验短路 、submitAsync 时序、错误态 |
| 浮层与定位 | resolveICEOverlayPosition 的翻转/夹取、关闭原因、动画帧推进 |
| 交互细节 | 表格排序/行悬停/行内按钮不触发行选中、菜单子项选中、树悬停、折叠箭头 |
| 键盘 | Tab 轮转、Enter 激活、Esc 关闭、方向键调值、下拉 ↑↓ |
| 数据组件 | 选择器过滤、级联路径、时间/日期取值、穿梭框搬运、上传校验 |
| 图表/展示 | 统计格式化与倒计时、日历网格、水印平铺、排版折行省略 |
| 布局与骨架 | ICESpace / ICEGrid 24 栅格、ICESplitter 夹取与拖动、容器不参与命中 |
| 焦点细节 | 焦点环 :focus-visible 策略(鼠标不画 / 键盘画 / 文本类 always) |
| 命名约定 | 导出与引擎零重名、类级导出必须 ICE 前缀、id 转发(tests/exports.unique.test.ts、tests/ICEIdentity.test.ts) |
写新组件的测试
- 先写
tests/ICEXxx.test.ts,按“规格”列 bullet(这个仓库的习惯); - 用上面的假 ICE 模板,覆盖:默认态、值变化、回调、禁用、表单取值、错误态;
- 有全局事件的(
mousedown/keydown/wheel)一定要测组件被移出场景后不崩(守卫); - 有浮层的,断言「打开 / 关闭 / 关闭原因」。