/** * 名片 DIY 组件 · 字号定制化三层口径契约测试 * * 锁死: diy_view.php 默认值 ↔ design.html 面板(slide+预览消费) ↔ C 端 vue 消费 * 口径: 字号字段一律 px 配置, C 端渲染 ×2 转 rpx(px2rpx); 默认值 = 历史硬编码值 * * 派生关系(保证默认渲染与历史一致): * - list 横滑竖卡: 姓名/信息比网格小一档(-1px) * - list 人数徽标: 比信息行小一档(-1px); 「查看全部」= 信息字号 * - entry 文字箭头的箭头: 比按钮文字大一档(+2px) */ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { resolve, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const BE = resolve(__dirname, '../../backend/src/addon/card'); const FE = resolve(__dirname, '../components-diy'); /** 三层口径定义: 字段 → [默认值(px), 消费侧期望字面量] */ const CONTRACT = { entry: { defaults: { titleSize: 16, subSize: 12, btnSize: 12 }, colors: { bgColorStart: '#2F6BFF', bgColorEnd: '#1E40AF', textColor: '#FFFFFF', accentColor: '#FFEB3B' }, layout: { borderRadius: 12, padding: 16 }, vueRead: 'this.v.', vueFile: 'diy-card-entry.vue', }, list: { defaults: { titleSize: 16, nameSize: 14, infoSize: 11 }, colors: { bgColor: '#FFFFFF', titleColor: '#333333', nameColor: '#1F2329', infoColor: '#888888' }, layout: { borderRadius: 12, padding: 12, gap: 10 }, vueRead: 'this.v.', vueFile: 'diy-card-list.vue', }, show: { defaults: { nameSize: 17, cardTitleSize: 13, subSize: 12, labelSize: 12, valueSize: 12, actSize: 12, btnSize: 13 }, colors: { bgColor: '#FFFFFF', nameColor: '#1F2329', infoColor: '#888888', labelColor: '#999999' }, layout: { borderRadius: 16, padding: 16 }, vueRead: 'this.c.', vueFile: 'diy-card-mini.vue', }, self: { defaults: { nameSize: 17, cardTitleSize: 13, subSize: 12, labelSize: 12, valueSize: 12, actSize: 12, btnSize: 13 }, colors: { bgColor: '#FFFFFF', nameColor: '#1F2329', infoColor: '#888888', labelColor: '#999999' }, layout: { borderRadius: 16, padding: 16 }, vueRead: 'this.c.', vueFile: 'diy-card-mini.vue', }, }; /** 从 diy_view.php 提取组件 value JSON(容忍单引号 PHP 串内的转义) */ const PHP_NAMES = { entry: 'DiyCardEntry', list: 'DiyCardList', show: 'DiyCardShow', self: 'DiyCardSelf' }; function readValueJson(component) { const php = readFileSync(resolve(BE, 'config/diy_view.php'), 'utf8'); const re = new RegExp("'name'\\s*=>\\s*'" + PHP_NAMES[component] + "'[\\s\\S]*?'value'\\s*=>\\s*'([^\']+)'"); const m = php.match(re); assert.ok(m, 'diy_view.php 中找到组件 ' + PHP_NAMES[component]); return JSON.parse(m[1]); } test('diy_view.php 四组件默认值包含全部字号字段且等于历史渲染值', () => { for (const [comp, cfg] of Object.entries(CONTRACT)) { const value = readValueJson(comp); for (const [field, dft] of Object.entries(cfg.defaults)) { assert.equal(Number(value[field]), dft, comp + '.' + field + ' 默认值应为 ' + dft); } } }); test('diy_view.php 配色/圆角间距默认值与契约一致(重置按钮的最终事实来源)', () => { for (const [comp, cfg] of Object.entries(CONTRACT)) { const value = readValueJson(comp); for (const [field, dft] of Object.entries(cfg.colors)) { assert.equal(String(value[field]).toUpperCase(), dft.toUpperCase(), comp + '.' + field + ' 颜色默认值应为 ' + dft); } for (const [field, dft] of Object.entries(cfg.layout)) { assert.equal(Number(value[field]), dft, comp + '.' + field + ' 默认值应为 ' + dft); } } }); test('design.html 样式面板提供一键恢复默认, 重置字段与默认值逐一对应', () => { for (const [comp, cfg] of Object.entries(CONTRACT)) { const html = readFileSync(resolve(BE, 'component/view/diy-card-' + comp + '/design.html'), 'utf8'); assert.ok(html.includes('diy-card-reset'), comp + ' 样式面板缺少恢复默认按钮'); const btn = html.match(/