Files
danyang-match/ui-tests/phase07-home-contracts.mjs
T
Codex ce172ffd4f feat(ui): activity 页迁 TS + 修复过期契约 + 新增 11 页静态预览器
## 背景
2026-09-13 UI 重设计方案的 13 个契约门禁中 3 个为红,逐一查明性质后分类处理。

## 1. activity 页迁移 TypeScript(11/11 页现已全 TS)
- activity.js → activity.ts(139 行),业务逻辑零改动,仅补类型
- 此前 STATUS.md 记「9/11 TS-only、3 页待迁」,但 messages/matchmaker
  已在 4f91be0 迁完,实际只剩 activity 一页,本提交后清零

## 2. miniapp-reference-contracts 契约自身过期(非代码问题)
- 契约引用 messages.js / matchmaker.js / activity.js,但页面已 TS 化,
  readFileSync 抛裸 ENOENT 栈,看不出「契约引用了陈旧文件名」这一真实原因
- 改为统一走 readPageLogic(page):断言 .ts 存在 + .js 不存在(锁单一运行源),
  找不到文件时给出可读断言而非崩溃
- 核心 11 页循环内统一加 TS-only 校验,防止 JS 回流

## 3. phase07-home-contracts 契约断言架构写反(非代码问题)
- 契约要求 HomeApplicationService 字面出现 userIdentityMapper.findByUserId,
  但实际是 capabilityService.isIdentityVerified(userId)(HomeApplicationService:116)
- 这符合 AGENTS.md #26「前端入口可见性由服务端 CapabilityService 返回决定」,
  让首页穿透 Capability 直接持有 mapper 反而是架构重复
- 且契约自身另一条已断言 CapabilityService 必须有 isIdentityVerified,
  两条要求互相矛盾
- 改为:查库断言落在 caps 侧;首页断言必须委托 capabilityService.isIdentityVerified;
  新增 doesNotMatch(home, /userIdentityMapper/) 禁止首页绕过 Capability
- 保留原有 doesNotMatch(home, /setVerified\(true\)/) 防硬编码断言

## 4. 新增 11 页静态预览器(本机无微信开发者工具)
- scripts/preview-miniapp.mjs:把 WXML + WXSS 转成可浏览器打开的静态 HTML
- 能力:wx:if/wx:elif/wx:else 链、wx:for 列表、嵌套三元、比较/取反运算、
  自定义组件按属性展开、slot 替换、rpx→px 换算、组件样式收集
- mock 数据严格对齐 types/index.d.ts 与各页 WXML 实际读取字段
  (如 regionName/heightCm/educationCode、status: registration_open),
  不用想象字段,避免误导验收判断
- 产出 artifacts/ui-redesign/miniapp/preview.html(11 页可切换,284K)
- 明确边界:非真机渲染、不执行页面 JS、不连后端,
  只用于视觉走查,不得作为功能验收证据

## 验证
- 契约门禁 12/13 通过(唯一红的 ui-document-baseline-contracts 涉及
  STATUS.md 验收口径,待用户看真机效果后裁决,本轮不动)
- 11 页渲染体量:116/70/47/56/94/68/39/39/41/37/38 个类名元素,
  零残留 {{}} 插值、零残留原始组件标签
- 小程序 tsc --noEmit 通过
2026-09-16 12:33:48 +08:00

61 lines
4.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict'
import fs from 'node:fs'
const read = (path) => fs.readFileSync(path, 'utf8')
const home = read('backend/src/main/java/com/danyang/match/platform/home/service/HomeApplicationService.java')
const caps = fs.existsSync('backend/src/main/java/com/danyang/match/platform/capability/service/CapabilityService.java')
? read('backend/src/main/java/com/danyang/match/platform/capability/service/CapabilityService.java')
: home // fallback: CapabilityService 独立化前逻辑在 HomeApplicationService 里
const miniapp = read('miniapp/pages/index/index.ts')
const wxml = read('miniapp/pages/index/index.wxml')
const project = JSON.parse(read('project.config.json'))
const labels = ['找对象', '公益红娘', '公益活动', '婚恋咨询', '婚恋课堂', '婚前辅导', '婚恋反诈', '善缘服务站']
let previous = -1
for (const label of labels) {
const position = home.indexOf(`"${label}"`)
assert.ok(position > previous, `首页入口顺序错误: ${label}`)
previous = position
}
// 首页实名状态必须来自数据库,但按 AGENTS.md #26 只能经由 CapabilityService 委托,
// 首页不得自行持有 userIdentityMapper 复制业务规则。查库断言落在 caps(CapabilityService)侧。
assert.match(caps, /userIdentityMapper\.findByUserId/, 'Capability 实名状态必须来自数据库')
assert.match(home, /capabilityService\.isIdentityVerified/, '首页实名状态必须委托 CapabilityService(AGENTS.md #26)')
assert.doesNotMatch(home, /userIdentityMapper/, '首页不得绕过 CapabilityService 直接查实名表')
assert.match(home, /recommendationEngineService\.getDailyRecommendations/, '首页推荐必须来自正式推荐引擎')
assert.match(home, /contentArticleMapper\.findPublishedAll/, '首页课堂内容必须来自内容模块')
assert.match(home, /serviceLocationMapper\.selectOne/, '首页服务站必须来自服务站模块')
assert.match(caps, /setDisabledReason/, '不可用入口必须返回服务端原因')
assert.match(caps, /isIdentityVerified/, 'Capability 必须校验真实实名状态')
assert.match(caps, /ACCOUNT_STATUS_NORMAL/, 'Capability 必须拒绝非正常账户')
assert.match(home, /\/pages\/counseling-intro\/counseling-intro/, '咨询服务建议必须跳转真实页面')
assert.match(home, /\/pages\/premarital-intro\/premarital-intro/, '婚前辅导建议必须跳转真实页面')
assert.doesNotMatch(home, /"pages\//, '服务端返回的小程序路由必须以斜杠开头')
assert.doesNotMatch(home, /setVerified\(true\)/, '禁止硬编码实名通过')
assert.doesNotMatch(home, /card\.setId\("default"\)/, '禁止硬编码服务站')
assert.deepEqual(project.setting.useCompilerPlugins, ['typescript'])
assert.ok(fs.existsSync('miniapp/pages/index/index.ts'))
assert.ok(!fs.existsSync('miniapp/pages/index/index.js'), '首页只能保留 TypeScript 运行源')
assert.ok(fs.existsSync('miniapp/pages/discover/discover.ts'))
assert.ok(!fs.existsSync('miniapp/pages/discover/discover.js'), '找对象页只能保留 TypeScript 运行源')
assert.match(miniapp, /disabledReason/, '小程序必须显示服务端 Disabled 原因')
assert.match(miniapp, /loadError: true/, '首页必须有网络错误状态')
assert.match(miniapp, /matchStore\.like\(userId\)/, '首页喜欢操作必须调用正式关系服务')
assert.match(miniapp, /matchStore\.pass\(userId\)/, '首页略过操作必须调用正式关系服务')
assert.doesNotMatch(miniapp, /setTimeout\(/, '首页不得保留演示用延时反馈')
assert.match(miniapp, /QUICK_ENTRIES\.map/, '服务端入口必须按固定业务顺序合并')
assert.match(wxml, /home-hero-v2\.jpg/, '首页默认 Banner 必须使用参考图夜景主视觉')
assert.doesNotMatch(wxml, />›</, '首页不得继续使用字符箭头')
const sections = ['brand-bar', 'status-strip', 'banner-section', 'entries-card', '今日推荐', '善缘公益服务', '近期公益活动', '善缘服务站', '婚恋课堂', '安全提醒']
previous = -1
for (const section of sections) {
const position = wxml.indexOf(section)
assert.ok(position > previous, `首页区块顺序错误: ${section}`)
previous = position
}
console.log('Phase 07 home contracts passed')