test(card): 名片级自定义二维码契约测试 + 修正陈旧断言

- 新增 tests/diy-card-qr-image-source.test.js(24 项):
  mini 基件 qrSrc 按「名片级 card.qr_image > 组件级 c.qrImage > 系统码
  card.qr_url」三层优先级取值(源码契约 + 真实求值双证);
  组件级 qrImage 仍完整保留(未被名片级顶掉); 反例防恒真
- diy-card-qr-style.test.js 补 2.20 盒模型/色带/逐向间距字段契约,
  并修正一处陈旧断言: cornerHeadStyle 的 paddingRight 已叠加
  2.20 逐向微调右位移(customRight), 旧正则未同步
This commit is contained in:
2026-10-27 14:52:30 +08:00
parent 0a5794d260
commit 523473250c
2 changed files with 391 additions and 9 deletions

View File

@@ -40,6 +40,13 @@ const miniSrc = strip(read('diy-card-mini.vue'));
const hasPanels = fs.existsSync(path.join(PANEL_DIR, 'diy-card-self/design.html'));
const showPanel = hasPanels ? strip(fs.readFileSync(path.join(PANEL_DIR, 'diy-card-show/design.html'), 'utf8')) : '';
const selfPanel = hasPanels ? strip(fs.readFileSync(path.join(PANEL_DIR, 'diy-card-self/design.html'), 'utf8')) : '';
const BOX_STYLE_FIELDS = [
'bandShow', 'bandBgMode', 'bandBg',
'wrapMarginTop', 'wrapMarginRight', 'wrapMarginBottom', 'wrapMarginLeft',
'headBgMode', 'headBg', 'headRadiusMode', 'headRadius', 'headSpacingCustom',
'headMarginTop', 'headMarginRight', 'headMarginBottom', 'headMarginLeft',
'headPaddingTop', 'headPaddingRight', 'headPaddingBottom', 'headPaddingLeft',
];
/**
* 面板控件的字段绑定有三种形态(2026-10-21 面板改版: 下拉框 → 可视化选项组,
@@ -172,20 +179,76 @@ test('common: qrTransform 位移(任一轴非零才输出; WXSS 支持 translate
// 2. mini.vue 基件契约
// =============================================================
test('mini: 蓝色顶带版式(2026-10-23 与精选名片 show 同款: 自适应白卡高度 + 弧线距底 88rpx)', () => {
// 模板: 卡片包进 wrap, 顶带在最前
test('mini: 顶部色带默认隐藏,背景支持同步名片设计或独立颜色', () => {
assert.match(miniSrc, /class="mini-card-wrap"/);
assert.match(miniSrc, /class="mini-band" :style="bandStyle"/);
assert.match(miniSrc, /<view v-if="bandShow" class="mini-band" :style="bandStyle"><\/view>/);
assert.match(miniSrc, /bandShow\(\)\s*\{\s*return flagOn\(this\.c\.bandShow, false\)/);
assert.match(miniSrc, /bandStyle\(\)\s*\{[\s\S]*?this\.c\.bandBgMode === 'custom'[\s\S]*?themeGradient\(this\.card\)/);
assert.match(miniSrc, /class="mini-card fade-up" :style="wrapStyle"/);
// 计算属性: 顶带底色可自定义(bandBg)或回落主题渐变; 高度可配(bandHeight)
assert.match(miniSrc, /bandStyle\(\)\s*\{[\s\S]*?bandBg[\s\S]*?themeGradient\(this\.card\)/);
// CSS: wrap 撑露沿 + band 拉伸(top:-40rpx / bottom:88rpx) + 圆角(顶直底弧)
// 保留原色带造型,显隐和背景只改内联消费,不改几何结构。
assert.match(miniSrc, /\.mini-card-wrap \{\s*position: relative;\s*margin-top: 40rpx;/);
assert.match(miniSrc, /\.mini-band \{\s*position: absolute;[\s\S]*?top: -40rpx;[\s\S]*?bottom: 88rpx;[\s\S]*?border-radius: 0 0 28rpx 28rpx;/);
// mini-block 方案不得回归
assert.ok(!miniSrc.includes('mini-block'));
});
test('mini: 卡片四向外边距与头部盒模型配置完整消费', () => {
for (const side of ['Top', 'Right', 'Bottom', 'Left']) {
assert.match(miniSrc, new RegExp('margin' + side + ': px2rpx\\(num\\(c\\.wrapMargin' + side + ', 0\\)\\)'));
assert.match(miniSrc, new RegExp('c\\.headMargin' + side));
assert.match(miniSrc, new RegExp('c\\.headPadding' + side));
}
assert.match(miniSrc, /headBoxStyle\(\)\s*\{[\s\S]*?c\.headBgMode === 'custom'[\s\S]*?this\.headTheme \? themeGradient\(this\.card\) : 'transparent'/);
assert.match(miniSrc, /headThemeStyle\(\)\s*\{\s*return this\.headBoxStyle/);
assert.match(miniSrc, /plainHeadFinalStyle\(\)\s*\{[\s\S]*?this\.cornerHeadStyle[\s\S]*?return \[this\.headBoxStyle, corner\]/);
});
test('mini: C端盒模型计算值——历史默认兼容且自定义值逐项生效', async () => {
const m = await import('../components-diy/diy-card-common.js');
const script = miniSrc.match(/<script>([\s\S]*?)<\/script>/)[1]
.replace(/import\s*\{[\s\S]*?\}\s*from\s*'\.\/diy-card-common\.js';/, '')
.replace('export default {', 'module.exports = {');
const names = Object.keys(m);
const moduleBox = { exports: {} };
new Function('module', ...names, script)(moduleBox, ...names.map(k => m[k]));
const computed = moduleBox.exports.computed;
const util = { objToStyle: o => o };
const card = { theme_color: '#119DFF', gradient_end: '#0E81D1', gradient_angle: 135 };
assert.equal(computed.bandShow.call({ c: {} }), false, '历史配置色带必须默认隐藏');
assert.equal(computed.bandStyle.call({ c: {}, card, $util: util }).background.includes('linear-gradient'), true);
assert.equal(computed.bandStyle.call({ c: { bandBgMode: 'custom', bandBg: '#123456' }, card, $util: util }).background, '#123456');
const wrap = computed.wrapStyle.call({
c: { wrapMarginTop: 11, wrapMarginRight: 12, wrapMarginBottom: 13, wrapMarginLeft: 14, padding: 22 },
card, $util: util,
});
assert.deepEqual([wrap.marginTop, wrap.marginRight, wrap.marginBottom, wrap.marginLeft], ['22rpx', '24rpx', '26rpx', '28rpx']);
assert.equal(wrap.padding, '44rpx');
const autoHead = computed.headBoxStyle.call({ c: { padding: 16 }, headTheme: true, card, $util: util });
assert.deepEqual(
[autoHead.marginTop, autoHead.marginRight, autoHead.marginBottom, autoHead.marginLeft],
['-32rpx', '-32rpx', '0rpx', '-32rpx']
);
assert.deepEqual(
[autoHead.paddingTop, autoHead.paddingRight, autoHead.paddingBottom, autoHead.paddingLeft],
['32rpx', '32rpx', '28rpx', '32rpx']
);
assert.equal(autoHead.borderRadius, '0 0 16rpx 16rpx');
const customCfg = {
headBgMode: 'custom', headBg: '#ABCDEF', headRadiusMode: 'custom', headRadius: 18, headSpacingCustom: 1,
headMarginTop: 1, headMarginRight: 2, headMarginBottom: 3, headMarginLeft: 4,
headPaddingTop: 5, headPaddingRight: 6, headPaddingBottom: 7, headPaddingLeft: 8,
};
for (const headTheme of [true, false]) {
const style = computed.headBoxStyle.call({ c: customCfg, headTheme, card, $util: util });
assert.equal(style.background, '#ABCDEF');
assert.equal(style.borderRadius, '36rpx');
assert.deepEqual([style.marginTop, style.marginRight, style.marginBottom, style.marginLeft], ['2rpx', '4rpx', '6rpx', '8rpx']);
assert.deepEqual([style.paddingTop, style.paddingRight, style.paddingBottom, style.paddingLeft], ['10rpx', '12rpx', '14rpx', '16rpx']);
}
});
test('mini: 企业徽标固定红底(参考图口径, 与头像底色解耦)', () => {
assert.match(miniSrc, /badgeStyle\(\)\s*\{\s*return this\.\$util\.objToStyle\(\{\s*background: '#FA5151'\s*\}\);/);
});
@@ -248,8 +311,9 @@ test('mini: cornerClearStyle 按实际尺寸反算让位(防遮挡, 纯数值不
test('mini: cornerHeadStyle 头部水平让位按浮层实际宽度动态反算', () => {
// 审计修复: 静态 180rpx 在自定义 qrSize>120 时不足 → 头部让位动态化
assert.match(miniSrc, /cornerHeadStyle\(\)\s*\{[\s\S]*?if \(this\.qrPos !== 'corner' \|\| !this\.showQrBase\) return \{\};/);
// 浮层宽[尺寸+衬底] + 右偏移 20 + 呼吸 20
assert.match(miniSrc, /paddingRight: \(size \+ pad \+ 40\) \+ 'rpx'/);
// 浮层宽[尺寸+衬底] + 右偏移 20 + 呼吸 20 + 2.20 逐向微调右位移(0=关)
assert.match(miniSrc, /const customRight = flagOn\(this\.c\.qrSpacingCustom, false\) \? px2rpx\(num\(this\.c\.qrMarginRight, 0\)\) : 0;/);
assert.match(miniSrc, /paddingRight: \(size \+ pad \+ 40 \+ customRight\) \+ 'rpx'/);
// 模板头部绑定了动态让位(浮层不存在时输出空对象不占位)
assert.match(miniSrc, /<view class="mini-head" :style="cornerHeadStyle">/);
});
@@ -274,6 +338,12 @@ test('self: miniConfig 透传头像+二维码新六字段', () => {
assert.match(selfSrc, /qrTipShow:\s*v\.qrTipShow/);
});
test('self: miniConfig 透传色带、卡片外边距与头部盒模型字段', () => {
for (const f of BOX_STYLE_FIELDS) {
assert.match(selfSrc, new RegExp(f + ':\\s*v\\.' + f + ','), 'miniConfig 缺透传 ' + f);
}
});
test('self: 「我的名片」角标与未登录提示条均已移除(2026-10-23 用户定稿)', () => {
// 角标按 2026-10-23 参考图删除, 反例防回归
assert.ok(!selfSrc.includes('self-badge') && !selfSrc.includes('slot="badge"'));
@@ -298,6 +368,7 @@ test('show: miniConfig 全字段透传(方案A定稿: C端=diy-card-mini 基件=
'headStyle', 'headNameColor', 'headSubColor', 'actsLayout', 'actGridColorMode',
'showRowsTitle', 'rowsTitle', 'showCopyBtn', 'actsPosition', 'actsBlockBg', 'actsBlockRadius', 'actsBlockPad',
'actIcons', 'actVis', 'actDeleted', 'actLabels', 'actOrder', 'customActs',
...BOX_STYLE_FIELDS,
];
for (const f of fields) {
assert.match(showSrc, new RegExp(f + ':\\s*v\\.' + f + ','), 'miniConfig 缺透传 ' + f);
@@ -329,6 +400,18 @@ for (const [name, panel] of hasPanels ? [['show', showPanel], ['self', selfPanel
}
});
test(name + ' 面板: 色带/卡片/头部盒模型字段均有控件且预览真实消费', () => {
const preview = panel.split('<template slot="edit-content">')[0];
for (const f of BOX_STYLE_FIELDS) {
assert.ok(bound(panel, f), '面板缺控件绑定 ' + f);
assert.ok(preview.includes('nc.' + f), '预览缺消费 ' + f);
}
assert.match(panel, /nc\.bandBgMode = 'theme'/);
assert.match(panel, /nc\.bandBgMode = 'custom'/);
assert.match(panel, /nc\.headBgMode = 'auto'/);
assert.match(panel, /nc\.headBgMode = 'custom'/);
});
test(name + ' 面板: 预览 mock 消费新属性(属性变更预览同步)', () => {
// 头像开关
assert.ok(panel.includes('nc.showAvatar !== false'), '预览不消费 showAvatar');