|
|
@@ -0,0 +1,137 @@
|
|
|
+/**
|
|
|
+ * 契约级单测(TDD-RED)——能力键 family.signature.healthPlanSummary(S-cfc-132)
|
|
|
+ *
|
|
|
+ * 溯源:
|
|
|
+ * requirement: docs/superpowers/specs/2026-11-07-signature-page-landscape.md
|
|
|
+ * §3 AC-R3(签署回传)、§5 WI-3(问卷完成签名改造:移除内嵌画布 → 「去签名」按钮 + 已签预览;
|
|
|
+ * 确认按钮仍依 signatureDataUrl 判空禁用)+ B-135-1(前端 16 红登记:
|
|
|
+ * pages/signature/components/ 子组件为真缺口,非假 RED)
|
|
|
+ * design: cfc-frontend/pages/signature/components/health-plan-summary.vue
|
|
|
+ * —— Step 3 健康计划摘要子组件(新建:展示 计划名 / 周期 / 项目 / 参与人;提供确认回调;实现仅在 feature 分支,
|
|
|
+ * 不得假定包路径,组件落地后本套件转绿)
|
|
|
+ *
|
|
|
+ * 说明:本文件与 __tests__/pages/signature/signature.healthPlan.test.js(S-cfc-52 键 family.signature.healthPlan)
|
|
|
+ * 锚定同一设计组件;本文件为能力键 family.signature.healthPlanSummary(S-cfc-132,B-135-1 重新登记键)的契约登记。
|
|
|
+ * 两套件均只读 .vue 源码做结构/行为契约断言,不求值 import、不修改 pages/、components/、utils/ 任何业务代码。
|
|
|
+ *
|
|
|
+ * TDD 现状(预期 RED):
|
|
|
+ * pages/signature/components/ 下当前仅有 hand-signature/,health-plan-summary.vue 尚未落地 →
|
|
|
+ * - 存在性断言失败(主红信号);
|
|
|
+ * - 契约断言经 loadTemplate/loadScript 加载组件时抛出「契约未落地」错误(溯源清晰的次红信号)。
|
|
|
+ * 实现方在后续 GREEN 阶段落地该组件并导出健康计划摘要契约后,本文件断言即翻绿。
|
|
|
+ */
|
|
|
+
|
|
|
+const fs = require('fs')
|
|
|
+const path = require('path')
|
|
|
+
|
|
|
+const HEALTH_PLAN_SUMMARY = path.join(
|
|
|
+ __dirname,
|
|
|
+ '../../../pages/signature/components/health-plan-summary.vue'
|
|
|
+)
|
|
|
+
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+// 工具:加载真实 SFC 的 <template>/<script> 文本(组件落地后生效;缺失时抛「契约未落地」)
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+
|
|
|
+function ensureExists() {
|
|
|
+ if (!fs.existsSync(HEALTH_PLAN_SUMMARY)) {
|
|
|
+ throw new Error('契约未落地:pages/signature/components/health-plan-summary.vue 不存在(GREEN 阶段需新建该组件)')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function loadTemplate() {
|
|
|
+ ensureExists()
|
|
|
+ const src = fs.readFileSync(HEALTH_PLAN_SUMMARY, 'utf8')
|
|
|
+ const m = src.match(/<template>([\s\S]*?)<\/template>/)
|
|
|
+ if (!m) throw new Error('health-plan-summary.vue 缺少 <template> 块')
|
|
|
+ return m[1]
|
|
|
+}
|
|
|
+
|
|
|
+function loadScript() {
|
|
|
+ ensureExists()
|
|
|
+ const src = fs.readFileSync(HEALTH_PLAN_SUMMARY, 'utf8')
|
|
|
+ const m = src.match(/<script>([\s\S]*?)<\/script>/)
|
|
|
+ if (!m) throw new Error('health-plan-summary.vue 缺少 <script> 块')
|
|
|
+ return m[1]
|
|
|
+}
|
|
|
+
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+// AC1 组件存在性(TDD-RED 主红信号)
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+
|
|
|
+describe('AC1 契约:health-plan-summary.vue 存在', function () {
|
|
|
+ test('组件文件 pages/signature/components/health-plan-summary.vue 存在', function () {
|
|
|
+ expect(fs.existsSync(HEALTH_PLAN_SUMMARY)).toBe(true)
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+// AC2 Step 3 展示健康计划摘要:计划名 / 周期 / 项目 / 参与人
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+
|
|
|
+describe('AC2 契约:Step 3 展示健康计划摘要(计划名/周期/项目/参与人)', function () {
|
|
|
+ test('展示「计划名」及数据绑定', function () {
|
|
|
+ const tpl = loadTemplate()
|
|
|
+ expect(tpl).toMatch(/计划名/)
|
|
|
+ expect(tpl).toMatch(/planName|plan\.name|plan_name/)
|
|
|
+ })
|
|
|
+
|
|
|
+ test('展示「周期」及数据绑定', function () {
|
|
|
+ const tpl = loadTemplate()
|
|
|
+ expect(tpl).toMatch(/周期/)
|
|
|
+ expect(tpl).toMatch(/cycle|cycleText|周期/)
|
|
|
+ })
|
|
|
+
|
|
|
+ test('展示「项目」列表(label + 列表渲染)', function () {
|
|
|
+ const tpl = loadTemplate()
|
|
|
+ expect(tpl).toMatch(/项目/)
|
|
|
+ expect(tpl).toMatch(/items|projects|planItems/)
|
|
|
+ expect(tpl).toMatch(/v-for/)
|
|
|
+ })
|
|
|
+
|
|
|
+ test('展示「参与人」列表(label + 列表渲染)', function () {
|
|
|
+ const tpl = loadTemplate()
|
|
|
+ expect(tpl).toMatch(/参与人/)
|
|
|
+ expect(tpl).toMatch(/participants|participant|members/)
|
|
|
+ expect(tpl).toMatch(/v-for/)
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+// AC3 提供确认回调
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+
|
|
|
+describe('AC3 契约:提供确认回调', function () {
|
|
|
+ test('模板将确认按钮绑定到 onConfirm(含「确认」文案)', function () {
|
|
|
+ const tpl = loadTemplate()
|
|
|
+ expect(tpl).toMatch(/@(?:click|tap)="onConfirm"/)
|
|
|
+ expect(tpl).toMatch(/确认/)
|
|
|
+ })
|
|
|
+
|
|
|
+ test('script 定义 onConfirm 方法并 emit confirm 事件', function () {
|
|
|
+ const script = loadScript()
|
|
|
+ expect(script).toMatch(/onConfirm\s*:/)
|
|
|
+ expect(script).toMatch(/emit\(['"]confirm/)
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+// AC4 Vue 组件导出
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+
|
|
|
+describe('AC4 契约:Vue 组件导出', function () {
|
|
|
+ test('script 通过 export default 导出组件(Option API)', function () {
|
|
|
+ const script = loadScript()
|
|
|
+ expect(script).toMatch(/export\s+default/)
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+// 权限流:健康计划签署页 Step 3 摘要组件面向「家长端问卷完成签署」,
|
|
|
+// 无角色门禁分支(家长/成长规划师均可用,规格未定义角色差异),
|
|
|
+// 属《编排派发》中「权限(适用时)」的不适用项,显式记录。
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+
|
|
|
+describe.skip('权限流:健康计划签署无角色门禁,不适用', function () {
|
|
|
+ test('占位:该能力面向问卷完成签署组件,无 role 分支(详见汇报)', function () {})
|
|
|
+})
|