|
@@ -0,0 +1,105 @@
|
|
|
|
|
+/**
|
|
|
|
|
+ * admin.family.planEntry 契约级单测(TDD-RED 阶段,预期红)
|
|
|
|
|
+ *
|
|
|
|
|
+ * 能力键:admin.family.planEntry
|
|
|
|
|
+ * 溯源·需求:docs/superpowers/specs/2026-11-08-family-admin-list-polish.md §需求3
|
|
|
|
|
+ * (方案数列 0 也渲染可点击链接 + plans 弹窗补「代生成方案」入口)
|
|
|
|
|
+ * 溯源·设计:cfc-web/src/views/Families.vue
|
|
|
|
|
+ * - L68-73 去掉 planCount>0 条件(planCount=0 也渲染可点击 el-link)
|
|
|
|
|
+ * - L229-248 plans 明细弹窗补「代生成方案」入口(跳转 HealthPlanManage 或内嵌生成表单)
|
|
|
|
|
+ *
|
|
|
|
|
+ * 【为何以「源码契约」而非「shallowMount 组件挂载」方式书写】
|
|
|
|
|
+ * 参考风格(ActivityAudit.spec.js)使用 shallowMount 挂载组件。但当前 cfclub 主链的
|
|
|
|
|
+ * Families.vue 模板存在一处先在的硬编译错误:查看成员弹窗中 `<el-empty v-else>`
|
|
|
|
|
+ * 嵌套在 `<el-table>`(其 v-if 挂在 table 自身)内部,vue-template-compiler 将其
|
|
|
|
|
+ * 列为 errors[],导致 vue-jest 无法加载整个组件(suite 直接编译失败)。本任务明确禁止
|
|
|
|
|
+ * 修改 src/main 实现,故无法绕过该先在障碍。GREEN 阶段落地本能力时会同步修正该嵌套;
|
|
|
|
|
+ * 届时可将本文件升级为 shallowMount 挂载式测试。
|
|
|
|
|
+ *
|
|
|
|
|
+ * 因此本文件采用「源码契约」断言:读取真实的 Families.vue 源码,断言设计契约所需
|
|
|
|
|
+ * 结构/行为缺失(RED)。GREEN 阶段实现后断言转绿。断言聚焦行为契约,不锁定实现细节
|
|
|
|
|
+ * (跳转路由 vs 内嵌表单二者任一即可)。
|
|
|
|
|
+ */
|
|
|
|
|
+import fs from 'fs'
|
|
|
|
|
+import path from 'path'
|
|
|
|
|
+
|
|
|
|
|
+const srcPath = path.resolve(__dirname, '../../views/Families.vue')
|
|
|
|
|
+const source = fs.readFileSync(srcPath, 'utf8')
|
|
|
|
|
+
|
|
|
|
|
+/** 取 planCount 列模板块(`<el-table-column prop="planCount"` 至其 `</el-table-column>`) */
|
|
|
|
|
+function planCountColumn() {
|
|
|
|
|
+ const start = source.indexOf('<el-table-column prop="planCount"')
|
|
|
|
|
+ expect(start).toBeGreaterThan(-1) // 方案数列必须存在(既有结构)
|
|
|
|
|
+ const end = source.indexOf('</el-table-column>', start)
|
|
|
|
|
+ return source.slice(start, end)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 取组件 script 段(`<script>` 至 `</script>`) */
|
|
|
|
|
+function scriptBlock() {
|
|
|
|
|
+ const start = source.indexOf('<script>')
|
|
|
|
|
+ const end = source.indexOf('</script>')
|
|
|
|
|
+ return source.slice(start, end)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 取 components 段声明(含所有子组件注册) */
|
|
|
|
|
+function componentsBlock() {
|
|
|
|
|
+ const m = source.match(/components\s*:\s*\{([^]*)\}/m)
|
|
|
|
|
+ return m ? m[1] : ''
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+describe('admin.family.planEntry 契约 (TDD-RED)', () => {
|
|
|
|
|
+ describe('AC1 · 列表 planCount=0 也渲染可点击链接', () => {
|
|
|
|
|
+ const col = () => planCountColumn()
|
|
|
|
|
+
|
|
|
|
|
+ it('planCount 列不应再包含 `planCount > 0` 门控(L68-73 契约要求去掉该条件)', () => {
|
|
|
|
|
+ expect(col()).not.toMatch(/planCount\s*>\s*0/)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('planCount 列不应再包含 `.cell-zero` 灰字 fallback(L68-73 契约要求改为 el-link)', () => {
|
|
|
|
|
+ expect(col()).not.toContain('cell-zero')
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('planCount 列的 el-link 不应被 `v-if="scope.row.planCount > 0"` 门控,且需保留 openDetailDialog 点击行为', () => {
|
|
|
|
|
+ expect(col()).toContain('<el-link')
|
|
|
|
|
+ expect(col()).toContain("openDetailDialog(scope.row.id, 'plans')")
|
|
|
|
|
+ // 契约核心:当前 el-link 被 planCount>0 门控包围(<el-link v-if="scope.row.planCount > 0"...),RED 阶段应断言其未被门控
|
|
|
|
|
+ expect(col()).not.toMatch(/<el-link[^>]*v-if\s*=\s*["']scope\.row\.planCount\s*>\s*0["']/)
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ describe('AC2 · plans 明细弹窗补「代生成方案」入口', () => {
|
|
|
|
|
+ /** 取 plans 明细弹窗区块(detailType === 'plans' 对应模板分支) */
|
|
|
|
|
+ function plansDialogBlock() {
|
|
|
|
|
+ const start = source.indexOf("detailType === 'plans'")
|
|
|
|
|
+ expect(start).toBeGreaterThan(-1) // 明细弹窗 plans 分支存在是既有契约
|
|
|
|
|
+ const end = source.indexOf("detailType ===", start + 1)
|
|
|
|
|
+ return source.slice(start, end > 0 ? end : start + 5000)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const dialog = () => plansDialogBlock()
|
|
|
|
|
+ const script = () => scriptBlock()
|
|
|
|
|
+
|
|
|
|
|
+ it('plans 明细弹窗区块内需出现「代生成方案」文案或语义标记(design anchor L229-248)', () => {
|
|
|
|
|
+ const hasEntry =
|
|
|
|
|
+ dialog().includes('代生成方案') ||
|
|
|
|
|
+ dialog().includes('plan-gen-entry') ||
|
|
|
|
|
+ dialog().includes('handleGeneratePlanOnBehalf') ||
|
|
|
|
|
+ dialog().includes('openPlanGen') ||
|
|
|
|
|
+ dialog().includes('goPlanGen')
|
|
|
|
|
+ expect(hasEntry).toBe(true)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('组件 script 需提供处理代生成方案流程的方法(跳转 HealthPlanManage 路由或触发内嵌生成表单)', () => {
|
|
|
|
|
+ const hasHandler =
|
|
|
|
|
+ /(openPlanGen|goPlanGen|handlePlanGen|showPlanGenForm)/.test(script()) ||
|
|
|
|
|
+ (/health-plan-manage/.test(script()))
|
|
|
|
|
+ expect(hasHandler).toBe(true)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('代生成方案流程需绑定 familyId(跳转时需携带 query.familyId 或复用 detailFamilyId)', () => {
|
|
|
|
|
+ const s = script()
|
|
|
|
|
+ expect(s).toMatch(/familyId/)
|
|
|
|
|
+ expect(s).toMatch(/health-plan-manage|HealthPlanManage/)
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+})
|