Files
lucky_shop/tests/diy-card-block-order.test.js
ZF sun 5e6eec166a feat(card): 名片区块流与基础信息补齐(2.22/2.23) + 分享带名片ID
- 组件支持六个「可独立成块」模块任意排序(区块流): 资料行/快捷操作/社交/二维码/
  标签来源的位置可调, 头部恒最上、发名片恒最下; 二维码位置档位归并为 flow。
- 补齐「名片基础信息」遗漏字段: 简介区块、社交区块、名片附加信息(过期遮罩/
  地址细分/来源), 并与 wap 端展示口径对齐。
- 小程序分享给游客时携带名片 ID: 分享 path 追加 card_id, 访客打开「我的名片」
  即看到分享者那张卡。新增 common/js/card-share.js 统一处理「取卡ID / 拼参数」
  (dataset 通道 + storage 通道兜底, 朋友圈场景靠 storage)。
2026-10-28 16:37:12 +08:00

99 lines
5.7 KiB
JavaScript

/**
* 名片 DIY 组件 ·「区块顺序」契约测试(2.23, 2026-10-28)
*
* 背景: 简介 / 资料区 / 快捷操作 / 社交媒体 / 二维码 / 标签来源 六个「可独立成块」的
* 模块此前是固定顺序(且快捷操作有 above/below 开关、二维码有 rows/bottom 档位),
* 2.23 统一为**区块流**: 顺序存 c.blockOrder, C 端按序渲染, 面板用 ↑↓ 调整。
*
* 覆盖(锁三个踩过的坑):
* 1. 区块流顺序解析(blockSeq): 配置优先 + 缺失 token 补齐 + 缺省按历史位置推导
* 2. 模板结构: <template v-for> 内必须用**独立 v-if**(Vue2 的 v-else-if 链跨迭代串联)
* 且 **template 不能带 :key**(Vue2 报错 "cannot be keyed")
* 3. 位置开关下线: 快捷操作上/下由顺序推导; 二维码 head/corner 是锚点档, 不进流内
*
* 跑法: node --test tests/diy-card-block-order.test.js
*/
'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const DIY_DIR = path.resolve(__dirname, '../components-diy');
const read = f => fs.readFileSync(path.join(DIY_DIR, f), 'utf8');
const strip = s => s
.replace(/<!--[\s\S]*?-->/g, '')
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/(^|[^:])\/\/.*$/gm, '$1');
const miniSrc = strip(read('diy-card-mini.vue'));
test('区块流: 常量与顺序解析', () => {
assert.match(miniSrc, /const BLOCK_TOKENS = \['bio', 'rows', 'acts', 'social', 'qr', 'meta'\]/,
'缺少 BLOCK_TOKENS(六个可独立成块的模块)');
assert.match(miniSrc, /const DEFAULT_BLOCK_ORDER = 'bio,acts,rows,social,qr,meta'/,
'缺少默认顺序(快捷操作在资料区之上, 兼容历史 actsPosition=above)');
assert.match(miniSrc, /blockSeq\(\)\s*\{[\s\S]*?this\.c\.blockOrder/,
'blockSeq 未读 c.blockOrder');
// 缺省回落: 按历史 actsPosition 推导快捷操作在资料区上/下
assert.match(miniSrc, /if \(!list\.length\) \{[\s\S]*?normalizeActsPosition\(this\.c\.actsPosition\) === 'above'/,
'缺省顺序未按历史 actsPosition 推导');
// 未知 token 忽略 + 缺失 token 补齐
assert.match(miniSrc, /BLOCK_TOKENS\.indexOf\(token\) >= 0 && list\.indexOf\(token\) < 0/,
'未过滤未知/重复 token');
assert.match(miniSrc, /def\.forEach\(\(t\) => \{[\s\S]*?if \(at < 0\) list\.unshift\(t\); else list\.splice\(at, 0, t\);/,
'缺失 token 未按默认顺序补齐');
});
test('区块流: 位置开关由顺序推导(旧开关只作兜底)', () => {
assert.match(miniSrc, /actsAbove\(\)\s*\{\s*const i = this\.blockSeq\.indexOf\('acts'\);\s*const r = this\.blockSeq\.indexOf\('rows'\);[\s\S]*?return i < r;/,
'actsAbove 未由区块顺序推导');
// 二维码: head/corner 是锚点档, 其余(rows/bottom/flow)进流内
assert.match(miniSrc, /showQrFlow\(\)\s*\{[\s\S]*?'flow'[\s\S]*?'rows'[\s\S]*?'bottom'/,
'showQrFlow 未覆盖 flow/rows/bottom 三档');
// 右上角浮层让位: 仅当资料区是流内首块
assert.match(miniSrc, /if \(this\.blockSeq\[0\] !== 'rows'\) return \{\};/,
'corner 让位未按区块顺序判据收窄');
});
test('区块流模板: template v-for 内用独立 v-if, 且 template 不带 :key', () => {
// Vue2: <template v-for> 内 v-else-if 链会跨迭代串联 → 必须全部独立 v-if
assert.doesNotMatch(miniSrc, /v-else-if="blk/, 'template v-for 内不得使用 v-else-if 链');
// Vue2: <template> cannot be keyed → key 放在真实元素上
const tmpl = miniSrc.match(/<template v-for="blk in blockSeq"[^>]*>/g) || [];
assert.equal(tmpl.length, 1, '应只有一个区块流 template');
assert.doesNotMatch(tmpl[0] || '', /:key=/, 'template v-for 不得带 :key(Vue2 报错)');
// key 下移到真实元素(快捷操作有宫格/芯片两个元素 → 真实元素数 7)
const keyed = miniSrc.match(/v-if="blk === '(bio|acts|rows|qr|social|meta)'[^>]*:key="blk"/g) || [];
assert.ok(keyed.length >= 6, '六个块的真实元素都应带 :key="blk", 实际 ' + keyed.length);
for (const blk of ['bio', 'acts', 'rows', 'qr', 'social', 'meta']) {
assert.match(miniSrc, new RegExp('v-if="blk === \'' + blk + '\'[^>]*:key="blk"'),
'块 ' + blk + ' 的真实元素未带 :key');
}
});
test('区块流: 六个块都被渲染且样式各自独立', () => {
for (const [blk, cls] of [['bio', 'mini-bio'], ['acts', 'mini-acts'], ['rows', 'mini-rows'],
['social', 'mini-social'], ['qr', 'mini-qr'], ['meta', 'mini-meta']]) {
assert.match(miniSrc, new RegExp('v-if="blk === \'' + blk + '\''), '缺少块 ' + blk + ' 的渲染分支');
}
assert.match(miniSrc, /class="mini-bio" :style="bioFinalStyle"/, '简介块未绑定独立样式');
assert.match(miniSrc, /class="mini-social" :style="socialFinalStyle"/, '社交块未绑定独立样式');
});
test('区块流: 通用工厂复用于新块(不新写一套)', () => {
assert.match(miniSrc, /function blockIndependent\(c, prefix\)/, '缺少 blockIndependent 工厂');
assert.match(miniSrc, /function blockFinalStyle\(c, prefix\)/, '缺少 blockFinalStyle 工厂');
assert.match(miniSrc, /bioFinalStyle\(\)\s*\{\s*return blockFinalStyle\(this\.c, 'bio'\);/, '简介未走通用工厂');
assert.match(miniSrc, /socialFinalStyle\(\)\s*\{\s*return blockFinalStyle\(this\.c, 'social'\);/, '社交未走通用工厂');
});
test('二维码档位: 新增 flow(卡片流内)', () => {
const common = strip(read('diy-card-common.js'));
assert.match(common, /export const QR_POSITIONS = \['flow', 'rows', 'head', 'corner', 'bottom'\]/,
'QR_POSITIONS 缺 flow 档');
assert.match(common, /export const QR_SIZES = \{ flow: 240,/, 'QR_SIZES 缺 flow 尺寸');
});