This repository has been archived on 2026-09-13. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
BPOS-TMP-REPO/packages/ui-kit/__tests__/view-container-multihost.test.ts
T
xcsandClaude Fable 5 66be5bab6e feat(w05): 数据工单去表格化 —— 翻面芯片卡(背面参数就地改)+ 基准瓷贴卡;平台多宿主收值
2026-08-31 用户第三轮:「界面上东西太多,做成几个区域;类型与核心参数字段做成和芯片卡
差不多的卡片;芯片卡与它的核心参数值合并 —— 点击翻转,背面写核心参数,悬浮要看得出能翻;
这个界面都不再需要表格来展示数据了」。

视图(主画面零表格,表格只活在新建类型抽屉里 —— 表单要加行,加行机制认 tr):
· 比对基准卡:类型丸 + 描述 + 字段瓷贴(名/硬软徽/单位/说明),与芯片卡同语言;
· 芯片卡翻面:正面主数据、背面本颗芯片的核心参数瓷贴(值必填红 / 说明建议橘)。
  翻转纯 CSS(checkbox + :has,零脚本;checkbox 无 data-col,collect 不认它),
  悬浮出「点击翻面 · 核心参数」提示丸 + 侧倾预告;主数据编辑锁正面、参数编辑自动
  翻背面(reduced-motion 下瞬时切换);说明四条收进折叠。

平台(ui-kit ViewContainer,多宿主收值):「参数值印在每张卡背、就地可改」= 同一张表
的行分居两张卡 —— 一个地址一个宿主装不下。hostsOf(querySelectorAll)+ 收值/开合/
镜像/快照全部按宿主清单走;**格子只归最近的 data-edit 祖先**(不然卡背参数会被主数据
的闸收走并进主数据行 —— collect-nested 钉过的静默丢数据换脸回来)。嵌套 data-rows
不带 data-edit 时行为原样(两条旧钉测不动);addRow 仍单宿主(会加行的表都是单宿主)。

途中踩了三脚都逮回:CSS 注释里写了「data-col=」原文把 ②③ 的「全只读」字面断言踩翻
(选择器与注释都不许出现);.flip-cue i 特异度压过 .cue-back{display:none} 两枚提示
丸同时冒出;host 计数断言错把渲染后的数当模版源码的数。

测:ui-kit multihost 3 条(排除规则变异验证)+ 包内 110(翻面/无表格/瓷贴新锚)+
2528 全仓四门绿;2.9.0 装库 SQL 核过(flip/back_host/ftiles/cue/主画面表数=2);
浏览器实测:点卡真翻(背面 5 瓷贴)、参数编辑自动翻背双卡成表单、LM 全值回显。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-31 15:36:39 +08:00

76 lines
4.4 KiB
TypeScript

/**
* **多宿主收值 + 嵌套异地址排除**(2026-08-31,W05 芯片卡翻面逼出的平台扩面)。
*
* 版面要求「参数值印在每张芯片卡的**背面**、就地可改」:参数表的行天然分居两张卡,
* 一个宿主装不下 —— `hostsOf` 把同一地址的全部宿主都数进来,每个宿主各自归行。
*
* 更要紧的是**嵌套异地址排除**:卡背的参数宿主(`data-edit="parts.parameter"`)住在
* 芯片卡宿主(`data-edit="parts.profile"`)的行里 —— 外层收值若把里层宿主的格子也
* 收走,参数值就会被当成主数据的列并进主数据的行(`view-container-collect-nested.test.ts`
* 钉过的静默丢数据,换一副面孔回来)。规则:**格子只归它最近的 `data-edit` 祖先**。
* 嵌套的 `data-rows` 不带 `data-edit` 时行为原样(照旧并行 —— 那两条钉测不动)。
*/
// @vitest-environment jsdom
import { describe, expect, it } from 'vitest';
import { collect, setOpenIn } from '../src/ViewContainer.tsx';
/** 两张芯片卡:正面是主数据格(profile),背面各住一个参数宿主(parameter) */
const FLIP_CARDS =
'<article data-edit="parts.profile">' +
'<section class="dossier">' +
'<div class="front"><input data-col="internal_pn" value="LM2596S-5.0"/><input data-col="brand_name" value="TI"/></div>' +
'<div class="back"><section class="pback" data-edit="parts.parameter">' +
'<div class="ptile"><input data-col="internal_pn" value="LM2596S-5.0"/><input data-col="param_name" value="输出电压"/><input data-col="param_value" value="5.0"/></div>' +
'<div class="ptile"><input data-col="internal_pn" value="LM2596S-5.0"/><input data-col="param_name" value="输出电流"/><input data-col="param_value" value="3"/></div>' +
'</section></div>' +
'</section>' +
'<section class="dossier">' +
'<div class="front"><input data-col="internal_pn" value="XL2596S-5.0"/><input data-col="brand_name" value="XLSEMI"/></div>' +
'<div class="back"><section class="pback" data-edit="parts.parameter">' +
'<div class="ptile"><input data-col="internal_pn" value="XL2596S-5.0"/><input data-col="param_name" value="输出电压"/><input data-col="param_value" value="5.0"/></div>' +
'<div class="ptile"><input data-col="internal_pn" value="XL2596S-5.0"/><input data-col="param_name" value="输出电流"/><input data-col="param_value" value="2.5"/></div>' +
'</section></div>' +
'</section>' +
'</article>' +
'<div class="jc-editgate" data-edit-for="parts.parameter"></div>';
function docOf(html: string): Document {
const doc = document.implementation.createHTMLDocument('');
doc.body.innerHTML = html;
return doc;
}
describe('多宿主 collect():同一地址的宿主都数进来,格子只归最近的 data-edit 祖先', () => {
it('parts.parameter 分居两张卡背的两个宿主 —— 4 个瓷贴收到 4 行,主键各自带对', () => {
const got = collect(docOf(FLIP_CARDS), 'parts.parameter');
expect(got).toHaveLength(4);
const xl = got.filter((r) => r['internal_pn'] === 'XL2596S-5.0');
expect(xl).toHaveLength(2);
expect(new Map(xl.map((r) => [r['param_name'], r['param_value']])).get('输出电流')).toBe('2.5');
});
it('parts.profile 收值**不含**卡背参数宿主的格子 —— 两行主数据,一列参数都不许混进来', () => {
const got = collect(docOf(FLIP_CARDS), 'parts.profile');
expect(got).toHaveLength(2);
for (const row of got) {
expect(row['brand_name']).toBeDefined();
/* 静默丢数据的那副面孔:param_value 若出现在主数据行里,就是里层宿主被外层吞了 */
expect(row['param_value']).toBeUndefined();
expect(row['param_name']).toBeUndefined();
}
});
it('setOpenIn:一把闸开合同一地址的**全部**宿主(非表格容器,.on 落在宿主自身)', () => {
const doc = docOf(FLIP_CARDS);
setOpenIn(doc, 'parts.parameter', true);
const hosts = Array.from(doc.querySelectorAll('.pback'));
expect(hosts).toHaveLength(2);
expect(hosts.every((h) => h.classList.contains('on'))).toBe(true);
expect(doc.querySelector('[data-edit-for="parts.parameter"]')?.classList.contains('on')).toBe(true);
/* 外层 profile 宿主不许被顺手点亮 —— 两把闸各管各的 */
expect(doc.querySelector('[data-edit="parts.profile"]')?.classList.contains('on')).toBe(false);
setOpenIn(doc, 'parts.parameter', false);
expect(hosts.every((h) => !h.classList.contains('on'))).toBe(true);
});
});