/** * **平坦退路形状:瓷贴是 `data-edit` 宿主的直接子元素,行容器是 `
` 不是 ``** * (2026-08-30,W05 数据工单版面探针)。 * * 对照 `view-container-collect-nested.test.ts` 那条「外层按料分组、内层铺瓷贴」的嵌套形状—— * 那种形状里瓷贴与 host 之间隔了两层(候选料的 `
` + 内层 `data-rows` 留下的 `
`), * `rowOf()` 爬到 host 直接子元素时只能到 `
`,把一颗料的 3 个瓷贴全部并成一条记录、 * 后写的列覆盖先写的,悄悄丢数据。 * * 这里是**能用的退路**:`data-rows="mine:parts.parameter"` 直接铺在 host(`data-edit` 那个元素) * 身上,不分料、不套内层 —— 瓷贴(`
`)因此是 host 的**直接子元素**,`rowOf()` * 第一步往上爬就撞到 host,一个瓷贴就是一行,互不干扰。W05 若要「一个候选一个卡片」的排版, * 换个思路做(每颗候选各给一张单独的 `data-edit` 宿主,或纯展示的分组包着这一层可编辑的平铺表), * 不能靠嵌套 `data-rows` 省一层。 * * `rowOf()`(`../src/ViewContainer.tsx:178`)按 `parentElement` 一路爬,不认标签名 —— * 现有 `view-container-collect.test.ts` 的用例全是 `` 行(表格),这里换成 `
` * 的瓷贴卡,证明它不是"认表格"这条窄路,是通用的"host 的直接子元素就是行"。 */ // @vitest-environment jsdom import { describe, expect, it } from 'vitest'; import { collect } from '../src/ViewContainer.tsx'; /** 两颗候选料、每颗 3 个参数瓷贴(与嵌套形状那条用同一组事实数据,方便对照) */ const TILES_HOST = '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
'; function docOf(html: string): Document { const doc = document.implementation.createHTMLDocument(''); doc.body.innerHTML = html; return doc; } describe('collect():瓷贴(
)是 data-edit 宿主的直接子元素 —— 一瓷贴一记录,互不覆盖', () => { it('6 个瓷贴收到 6 条记录(不是并成 2 条候选级记录)', () => { const got = collect(docOf(TILES_HOST), 'parts.parameter'); expect(got).toHaveLength(6); }); it('每条记录三格各自带对:主键 internal_pn + param_key + param_value,不互相覆盖', () => { const got = collect(docOf(TILES_HOST), 'parts.parameter'); // 同一颗料的 3 条参数各自独立成行,3 个键值全部保留(与嵌套形状那条"只剩最后一瓷贴"相反) const lm = got.filter((r) => r['internal_pn'] === 'LM2596S-5.0'); expect(lm).toHaveLength(3); const byKey = new Map(lm.map((r) => [r['param_key'], r['param_value']])); expect(byKey.get('input_voltage')).toBe('4.5~40'); expect(byKey.get('output_voltage')).toBe('5'); expect(byKey.get('enable_threshold')).toBe('2.5'); const xl = got.filter((r) => r['internal_pn'] === 'XL2596S-5.0'); expect(xl).toHaveLength(3); expect(new Map(xl.map((r) => [r['param_key'], r['param_value']])).get('enable_threshold')).toBe('3.0'); }); /* * 行容器换一个跟表格毫无关系的标签(`
  • `)结果不变 —— 证明 `rowOf` 认的是 * "host 的直接子元素",不是专挑某个标签名。 * * 特意不拿 `` 来比:裸 ``(不包在 `` 里)会撞上 HTML 解析算法的 * "foster parenting"(表格内容规则规定 `` 只能直接装 `` 外面)——真拿裸 tr 包 input 试过,jsdom 吐出的是 * 9 条(3 个空 `` + 6 个被挪出来的 `` 各自成行),这是 HTML 解析的固有行为, * 与 `rowOf` 是否"认标签"无关,拿来当反例只会误导。已有的 `view-container-collect.test.ts` * 用 `
    /`,`` 这类 * 不合规内容会被解析器整体挪到 `
    ` 包着的 `` 早就证明过 tr 这条路是对的。 */ it('行容器换成完全不相干的标签(
  • )结果不变 —— 证明 rowOf 认的是"host 的直接子元素",不是专挑某个标签名', () => { const liHost = '
    ' + '
  • ' + '
  • ' + '
  • ' + ''; const got = collect(docOf(liHost), 'parts.parameter'); expect(got).toHaveLength(3); expect(new Set(got.map((r) => r['param_key']))).toEqual(new Set(['input_voltage', 'output_voltage', 'enable_threshold'])); }); it('变异验证:去掉一个瓷贴整行,记录数跟着从 6 变成 5 —— 证明「6 条」不是巧合,是按瓷贴数记', () => { const oneLess = TILES_HOST.replace( '
    ', '', ); const got = collect(docOf(oneLess), 'parts.parameter'); expect(got).toHaveLength(5); }); it('变异验证:摘掉一个瓷贴的主键 data-col(internal_pn)—— 只有那一条记录缺这一列,其余 5 条不受影响', () => { const withoutOnePk = TILES_HOST.replace( '', '', ); const got = collect(docOf(withoutOnePk), 'parts.parameter'); expect(got).toHaveLength(6); const missingPk = got.filter((r) => !('internal_pn' in r)); expect(missingPk).toHaveLength(1); expect(missingPk[0]?.['param_key']).toBe('enable_threshold'); expect(missingPk[0]?.['param_value']).toBe('2.5'); }); });