/** * ACT-WORK-05 差异评估的包内自测 —— **只准 import @sdk / @contracts / @sfeel**。 * 案例活动没有 `run`,自测不跑回合;测的是清单本身与授权面对齐,结构照 E04 / I01。 * * 七组:清单本身合规 / 台账声明 / 案例模型与清单的对齐 / 报告模版:编辑面与只读面各就各位 / * 徽标别名与值域相关的坑(2026-08-27 实测账,回归网)/ 可编辑件的主键与必填列 * (2026-08-27 复审,回归网)/ 活动入口。 */ import { describe, expect, it } from 'vitest'; import { activityManifestSchema } from '@contracts'; import { createMockHost } from '@sdk'; import { manifest, D } from '../src/manifest.ts'; import activity from '../src/index.ts'; /* * 2026-08-29 Task 3 整体重写(spec §4):九步砍成四步,案例模型换成 CMMN + `case-model.ts` 里的 * `ASSESSMENT_STEPS` 四条剧本。这份包内自测原是照旧九步写的,`案例模型与清单的对齐` 这组的多数 * 断言在结构上仍然成立(CMMN humanTask id 与剧本 item 一一对应、除放行外每项恰属一个阶段…), * 只是数字与名单要随新模型换 —— 下面这几处常量与断言标题已按 `case-model.ts` 的新四步同步更新。 */ const ITEMS = ['S1_inputs', 'S2_assess', 'S3_review', 'T_release']; /* * 2026-08-29 Task 3:四步里只有 `S3_review`(审核,可循环)与 `T_release`(放行)不给 * `doneWhen`——判据不许摆在还欠一张卡的那一步眼前(2026-08-28 用户实测逼出的立墙, * `humanWrites`/`acceptanceChecklist` 同一课)。`S1_inputs` / `S2_assess` 全 auto, * 入库事实 / 分析结论一落地引擎就自己收步,不再需要人工激活等待。 */ const NO_DONE_WHEN = new Set(['S3_review', 'T_release']); /* * 2026-08-29 Task 7:五张旧局面收成三张 —— `p08-master-create` / `p10-document-create` / * `eval-input-list` 三张合并成一张 `input-supplement`(Task 7 已交付); * `difference-review` / `landing-confirm` 由 Task 8 / 9 各自重做(同名)。 */ const VIEWS = ['input-supplement', 'difference-review', 'landing-confirm']; /** 运营中心 L2 沙箱里那张关键台账数据看板 —— 不是工单件,只在「都声明了」这一条里出现;自己那组断言在文件尾 */ const BOARD_VIEW = 'activity-ledger'; const byAddress = (domain: string, object: string) => manifest.declarations.ledger.find((l) => l.domain === domain && l.object === object); const viewHtml = (name: string) => manifest.assets.viewTemplates.find((t) => t.name === name)?.html ?? ''; /* 「台账声明」组用的短写(2026-08-29 重写引入):地址是 `域.对象` 一整串,`byAddress` 拆两段 */ const ledgerOf = (address: string) => manifest.declarations.ledger.find((l) => `${l.domain}.${l.object}` === address); const colOf = (address: string, name: string) => ledgerOf(address)?.fields.find((f) => f.name === name); /** * 从一段 CSS 文本里摘出**选择器里提到过的所有 class token**(2026-08-30 task-12,缺陷③用)。 * 只扫选择器,不扫规则体(`content:"…"` 这类属性值里的文字不算);`@media`/`@supports` * 这类带嵌套一层规则体的 at-rule 会往里再走一遍(本仓三份件的 `@media(max-width:600px)` 与 * `@media (prefers-reduced-motion: reduce)` 都嵌着真正的选择器,不能漏)。 * 这是**存在性**扫描(选择器里出没出现过这个 class),不判断它是不是复合选择器的一部分 —— * `.zic` 完全不存在是这份扫描要抓的那种漏(缺陷③的两个真实事故都是这一种:某个 class * 在目标件自己的 `')); expect(body).not.toContain('gateprog'); expect(body).not.toContain('class="viewarea"'); expect(body).not.toContain('class="editarea"'); /* .editarea{display:none} 还在 CSS 里 —— 谁把内容包进它,那块就永远消失 */ expect(html).toContain('.editarea{display:none}'); }); it('**保存即铺格之前的空态**:pvnull 按 parts_parameter_rows 的 v-0 档现身(零行才摆提示,有行不摆 —— 界面不撒谎)', () => { expect(html).toContain('class="pv-empty pvnull" data-class-of="parts_parameter_rows"'); expect(html).toContain('.pvnull{display:none}'); expect(html).toContain('.pv-empty.pvnull.v-0{display:block}'); }); }); /* * 件② 差异评估结论审核(2026-08-29,Task 8):S3 的验收工单,`view: 'difference-review'` * 指向它,`cardVariant: 'review'`。绑定契约(spec §6.2):对决头(原 PN → 候选 PN、类型徽)+ * 四态分布带 + 推荐等级大徽章只读;逐条明细可就地改(四态 / 影响说明 / 判断依据 / UNKNOWN 去向); * 收口四格(推荐理由 / 差异摘要 / 风险 / 验证要求)只读 —— 它们是配方按规则算的,人要改就去改 * 上面那一条四态,系统重算。复用文件顶部已有的 `viewHtml` 短写。 */ describe('件② 差异评估结论审核', () => { const html = viewHtml('difference-review'); it('在 viewTemplates 里,S3 指向它,面孔是审核卡', () => { expect(html).not.toBe(''); const s3 = (manifest.assets.businessModel.steps as ReadonlyArray<{ item: string; view?: string; cardVariant?: string }>) .find((s) => s.item === 'S3_review'); expect(s3?.view).toBe('difference-review'); expect(s3?.cardVariant).toBe('review'); }); it('逐条明细可改,且**改的是四态**', () => { expect(html).toContain('data-rows="mine:substitution.difference_item"'); expect(html).toContain('data-edit="substitution.difference_item"'); expect(html).toContain('data-col="difference_status"'); for (const col of ['impact_statement', 'evidence_note', 'unknown_disposition']) { expect(html).toContain(`data-col="${col}"`); } expect(html).toContain('data-col="difference_item_id"'); }); it('**推荐理由 / 摘要 / 风险 / 验证要求只读** —— 让人直接改摘要,等于让他给自己没做的判断盖章', () => { for (const col of ['recommendation_reason', 'difference_summary', 'verification_requirement']) { expect(html).toContain(`data-slot="${col}"`); expect(html).not.toContain(`data-col="${col}"`); } expect(html).not.toContain('data-edit="substitution.relation"'); }); it('等级与四态用 data-class-of 上色 —— 判断留在样式表,模版不判', () => { expect(html).toContain('data-class-of="recommendation_level"'); expect(html).not.toContain(' { for (const col of [ 'parameter_name', 'parameter_desc', 'constraint_type', 'original_value', 'candidate_value', 'parameter_unit', 'recommendation_level', 'risk_level', ]) { expect(html, col).not.toContain(`data-col="${col}"`); } }); }); /* * 件③ 落账确认(2026-08-29,Task 9):T_release 的终局验收工单,`view: 'landing-confirm'` 指向它。 * **全只读,答「这一签下去发生什么」(spec §6.3)**:进哪几张表各几行(摆人读名)· 结论卡 * (等级 + 一句话)· 四态分布并把硬约束不符的逐条点名 · 待验项(P-06 的测试条件与后续动作)· * 发哪一口。复用文件顶部已有的 `viewHtml` 短写。 */ describe('件③ 落账确认', () => { const html = viewHtml('landing-confirm'); it('在 viewTemplates 里,T_release 指向它', () => { expect(html).not.toBe(''); expect((manifest.assets.businessModel.steps as ReadonlyArray<{ item: string; view?: string }>) .find((s) => s.item === 'T_release')?.view).toBe('landing-confirm'); }); /* * **`data-edit=` 这个子串照不到编辑闸的外壳**(2026-08-29 评审自拟变异逮到的盲点): * `editGate()` / `addrow()` 产出的是 `data-edit-open=` / `-cancel=` / `-save=` / `-for=` / * `-add=` / `-del=` —— `edit` 后面跟的是连字符,**永远凑不出 `data-edit=`**。 * 实测:往这份件里插一个孤立的 `