/** * **嵌套 `data-rows`(外层按料分组、内层铺参数瓷贴)喂给真实 `collect()`,会并成一条记录** * (2026-08-30,W05 数据工单版面探针 —— 探的是「一个候选一个 section、section 里铺参数瓷贴」 * 这形状能不能拿来做可编辑版面)。 * * 这段夹具字符串 **原样照抄自** `apps/api/src/modules/interaction/__tests__/ * view-render-nested-editable.test.ts` 里 `renderView(NESTED_TPL, NESTED_SCOPE)` 的真实产出 * (该文件断言过逐字节相等,变量名同为 `NESTED_RENDERED`)——两侧各钉一条是因为 `collect()` * 住在 `@ui`(tsconfig.web.json:`jsx: react-jsx` + DOM lib)、`renderView` 住在 `apps/api` * (tsconfig.node.json:两者都没有对方要的编译选项),`packages-are-leaves` 门禁也不许 * `packages/*` 反向 import `apps/*`——两侧不能用同一份代码/常量接力,只能用**同一段字符串** * 接力,不一致就靠这条注释与两处的断言互相盯着。 * * ── 结论 ────────────────────────────────────────────────────────────────── * `rowOf()` 从格子往上爬,爬到 host(`[data-edit="parts.parameter"]`,即 `
`)的 * **直接子元素**为止——嵌套形状里那正是每颗候选料的 `
`,不是瓷贴自己的 `` * (瓷贴与 section 之间还隔着内层 `data-rows` 留下的那层 `
`)。于是一颗料的 3 个瓷贴全部 * 归到同一个 `
` 名下:`collect()` 只吐出 **2 条记录**(candidate 数,不是瓷贴数), * 且同一列被后一个瓷贴的值**悄悄覆盖**前一个——`param_key`/`param_value` 最终只剩最后一颗瓷贴 * (`enable_threshold`)的值,`input_voltage`/`output_voltage` 两条参数的编辑结果整个丢失, * 界面上却看不出任何报错。**所以数据工单版面不许这么嵌**——要么瓷贴与 host 之间只隔一层 * (见 `view-container-collect-div-rows.test.ts` 的平坦退路形状),要么给每个瓷贴单独一张 * `data-edit` 宿主。 */ // @vitest-environment jsdom import { describe, expect, it } from 'vitest'; import { collect } from '../src/ViewContainer.tsx'; /** 与 view-render-nested-editable.test.ts 的 `NESTED_RENDERED` 逐字节相同 —— 见该文件头注 */ const NESTED_RENDERED = '

LM2596S-5.0

' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '

XL2596S-5.0

' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
'; function docOf(html: string): Document { const doc = document.implementation.createHTMLDocument(''); doc.body.innerHTML = html; return doc; } describe('collect():外层 data-rows 分组、内层 data-rows 铺瓷贴 —— 一个候选的三个瓷贴并成一条记录', () => { it('两颗候选料只收到 2 条记录,不是 6 条(3 瓷贴 × 2 料)——瓷贴被 section 并行掉了', () => { const got = collect(docOf(NESTED_RENDERED), 'parts.parameter'); expect(got).toHaveLength(2); }); it('同一 section 内后一个瓷贴的列值覆盖前一个:param_key/param_value 只剩最后一瓷贴(enable_threshold),前两条参数的编辑结果丢失', () => { const got = collect(docOf(NESTED_RENDERED), 'parts.parameter'); const byPn = new Map(got.map((r) => [r['internal_pn'], r])); // 3 个瓷贴按 DOM 序是 input_voltage → output_voltage → enable_threshold,最后写的赢 expect(byPn.get('LM2596S-5.0')).toEqual({ param_key: 'enable_threshold', param_value: '2.5', internal_pn: 'LM2596S-5.0' }); expect(byPn.get('XL2596S-5.0')).toEqual({ param_key: 'enable_threshold', param_value: '3.0', internal_pn: 'XL2596S-5.0' }); // input_voltage / output_voltage 两条参数的值,在 collect() 的输出里完全找不到痕迹 expect(JSON.stringify(got)).not.toContain('input_voltage'); expect(JSON.stringify(got)).not.toContain('output_voltage'); }); it('变异验证:去掉一整颗候选料的 3 个瓷贴,记录数跟着从 2 变成 1 —— 证明上面「2 条」不是巧合,是真的按 section 数记', () => { const oneSection = NESTED_RENDERED.slice(0, NESTED_RENDERED.indexOf('
') + '
'.length) + '
'; const got = collect(docOf(oneSection), 'parts.parameter'); expect(got).toHaveLength(1); }); it('变异验证:摘掉主键 data-col(internal_pn)—— 记录里就再也找不到这一列(证明断言真的在读 data-col,不是巧合)', () => { const withoutPk = NESTED_RENDERED.replaceAll('', '').replaceAll( '', '', ); const got = collect(docOf(withoutPk), 'parts.parameter'); for (const row of got) expect(row).not.toHaveProperty('internal_pn'); }); });