feat(diy-card): A+B 视觉升级(资料行图标/可点 + 二维码四档任意位)
配合后端 f031476, 组件内直接复刻参考图视觉, 功能仍归详情页(路径A): - common.js: rowIconFor(field→iconfont glyph, 无字形退化空串) + rowActOf(href 派生 tel/mail/copy, 空 href 不可点) + normalizeQrPosition/QR_SIZES(四档归一化+尺寸表) - mini.vue: 资料行图标列(与文字标签列同宽, 值列左缘对齐) + 行点击 emit row-act; 二维码四档渲染: rows 行下(默认) / head 头部右侧(三段 flex 零遮挡) / corner 右上浮层(白底衬底+Logo 避让+资料行让位 108rpx) / bottom 主按钮上方; 二维码尺寸随档位(180/120/120/180) - show/self: miniConfig 透传 showRowIcons/rowTap/qrPosition, 模板挂 @row-act="doAct" 复用既有动作分发 - 新增 tests/diy-card-ab-visual.test.js 13 项(真函数+源码断言, 7 项变异验证全抓住) node --test tests/diy-card-ab-visual.test.js → 13/13
This commit is contained in:
@@ -128,3 +128,41 @@ export function visibleRows(rows, max) {
|
||||
}
|
||||
return { shown: list, rest: [], hasMore: false };
|
||||
}
|
||||
|
||||
/**
|
||||
* 资料行图标(参考图: 电话/微信/地址带头像, 邮箱/网站/自定义无字形→退化为文字标签)
|
||||
* 口径与 pages_tool/card/detail.vue 的 fieldRows 映射一致(同 glyph 同规则)
|
||||
*/
|
||||
const ROW_ICONS = {
|
||||
mobile: 'icon-dianhua',
|
||||
phone: 'icon-dianhua',
|
||||
wechat: 'icon-weixin',
|
||||
address: 'icon-dizhi',
|
||||
};
|
||||
export function rowIconFor(field) {
|
||||
return ROW_ICONS[String(field || '')] || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 资料行点击动作: 由服务端下发的 href 派生(tel:/mailto:/url/空)
|
||||
* tel: → 拨号 mailto: → 邮箱(H5) / 复制(其它端)
|
||||
* http(s) → 复制链接 空 → 无动作(纯展示, 如地址/自定义)
|
||||
* 返回 null 表示不可点; type 取值与父组件 doAct 的 tel/mail/copy 分支对齐
|
||||
*/
|
||||
export function rowActOf(row) {
|
||||
const href = String((row && row.href) || '').trim();
|
||||
const value = String((row && row.value) || '').trim();
|
||||
if (!href || !value) return null;
|
||||
if (/^tel:/i.test(href)) return { type: 'tel', value: value };
|
||||
if (/^mailto:/i.test(href)) return { type: 'mail', value: value };
|
||||
return { type: 'copy', value: value };
|
||||
}
|
||||
|
||||
/** 二维码位置(四档): rows 资料行下方(默认) / head 头部右侧 / corner 右上角浮层 / bottom 主按钮上方 */
|
||||
export const QR_POSITIONS = ['rows', 'head', 'corner', 'bottom'];
|
||||
export function normalizeQrPosition(v) {
|
||||
const s = String(v || '').trim();
|
||||
return QR_POSITIONS.indexOf(s) >= 0 ? s : 'rows';
|
||||
}
|
||||
/** 各档位的二维码边长(rpx): 头部右侧紧凑, 浮层带衬底, 行下/底部大图便于扫 */
|
||||
export const QR_SIZES = { rows: 180, head: 120, corner: 120, bottom: 180 };
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</view>
|
||||
|
||||
<!-- 名片卡 -->
|
||||
<view v-else class="mini-card fade-up" :style="wrapStyle">
|
||||
<view v-else class="mini-card fade-up" :style="wrapStyle" :class="{ 'qr-at-corner': qrPos === 'corner' }">
|
||||
<!-- 角标插槽(如 self 组件的「我的名片」) -->
|
||||
<view class="mini-badge-slot">
|
||||
<slot name="badge"></slot>
|
||||
@@ -36,9 +36,15 @@
|
||||
<!-- 主题渐变饰条(可选) -->
|
||||
<view v-if="showGradient" class="mini-gradient" :style="gradientStyle"></view>
|
||||
|
||||
<!-- Logo(可选, 右上) -->
|
||||
<!-- Logo(可选, 右上; corner 档二维码占同一位, 自动避让) -->
|
||||
<image v-if="showLogo && card.logo" class="mini-logo" :src="$util.img(card.logo)" mode="aspectFit" />
|
||||
|
||||
<!-- 二维码·右上角浮层(corner 档: 白底衬底保可扫, 资料行自动让位) -->
|
||||
<view v-if="showQrCorner" class="mini-qr mini-qr-corner">
|
||||
<image class="qr-img" :src="$util.img(card.qr_url)" :style="qrImgStyle" mode="aspectFit" />
|
||||
<text class="qr-tip" :style="labelStyle">{{ qrTip || '扫码查看完整名片' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="mini-head">
|
||||
<view class="mini-avatar" :style="avatarStyle">
|
||||
<image v-if="card.avatar" class="avatar-img" :src="$util.img(card.avatar)" mode="aspectFill" />
|
||||
@@ -52,12 +58,19 @@
|
||||
<text v-if="card.card_title" class="mini-card-title" :style="subTextStyle">{{ card.card_title }}</text>
|
||||
<text v-if="subLine" class="mini-sub" :style="subTextStyle">{{ subLine }}</text>
|
||||
</view>
|
||||
<!-- 二维码·头部右侧(head 档: 头像|信息|二维码 三段 flex, 零遮挡) -->
|
||||
<view v-if="showQrHead" class="mini-qr mini-qr-head">
|
||||
<image class="qr-img" :src="$util.img(card.qr_url)" :style="qrImgStyle" mode="aspectFit" />
|
||||
<text class="qr-tip" :style="labelStyle">{{ qrTip || '扫码查看完整名片' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 资料行(可折叠) -->
|
||||
<!-- 资料行(可折叠; 有字形字段显图标, 无字形退化为文字标签) -->
|
||||
<view v-if="showRowsBlock && shownRows.length" class="mini-rows">
|
||||
<view v-for="(row, i) in shownRows" :key="i" class="mini-row">
|
||||
<text class="row-label" :style="labelStyle">{{ row.label }}</text>
|
||||
<view v-for="(row, i) in shownRows" :key="i" class="mini-row"
|
||||
:class="{ 'row-tappable': rowTapable(row) }" :hover-class="rowTapable(row) ? 'btn-press' : 'none'" @tap="onRowTap(row)">
|
||||
<text v-if="rowIcon(row)" class="row-icon iconfont" :class="rowIcon(row)" :style="labelStyle"></text>
|
||||
<text v-else class="row-label" :style="labelStyle">{{ row.label }}</text>
|
||||
<text class="row-value" :style="valueStyle">{{ row.value }}</text>
|
||||
</view>
|
||||
<view v-if="hasMoreRows" class="row-toggle" hover-class="btn-press" @tap="expanded = !expanded">
|
||||
@@ -65,6 +78,12 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 二维码·资料行下方(rows 档, 默认) -->
|
||||
<view v-if="showQrRows" class="mini-qr">
|
||||
<image class="qr-img" :src="$util.img(card.qr_url)" :style="qrImgStyle" mode="aspectFit" />
|
||||
<text class="qr-tip" :style="labelStyle">{{ qrTip || '扫码查看完整名片' }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 快捷操作(芯片, 最小触达 72rpx; 首个为主动作) -->
|
||||
<view v-if="showActs && acts.length" class="mini-acts">
|
||||
<view v-for="(act, i) in acts" :key="i" class="act-chip"
|
||||
@@ -73,9 +92,9 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 二维码(可选) -->
|
||||
<view v-if="showQr && card.qr_url" class="mini-qr">
|
||||
<image class="qr-img" :src="$util.img(card.qr_url)" mode="aspectFit" />
|
||||
<!-- 二维码·主按钮上方(bottom 档) -->
|
||||
<view v-if="showQrBottom" class="mini-qr">
|
||||
<image class="qr-img" :src="$util.img(card.qr_url)" :style="qrImgStyle" mode="aspectFit" />
|
||||
<text class="qr-tip" :style="labelStyle">{{ qrTip || '扫码查看完整名片' }}</text>
|
||||
</view>
|
||||
|
||||
@@ -92,6 +111,7 @@
|
||||
// 消费名片自身设计资产: cover 封面 / logo / theme_color / 渐变末色与角度 / card_title
|
||||
import {
|
||||
CardTokens, px2rpx, flagOn, num, initialOf, avatarBg, themeGradient, visibleRows,
|
||||
rowIconFor, rowActOf, normalizeQrPosition, QR_SIZES,
|
||||
} from './diy-card-common.js';
|
||||
|
||||
export default {
|
||||
@@ -126,7 +146,8 @@ export default {
|
||||
return flagOn(this.c.showGradient, false);
|
||||
},
|
||||
showLogo() {
|
||||
return flagOn(this.c.showLogo, false);
|
||||
// corner 档二维码占右上同一位, Logo 自动避让(面板有提示)
|
||||
return flagOn(this.c.showLogo, false) && this.qrPos !== 'corner';
|
||||
},
|
||||
showActs() {
|
||||
return flagOn(this.c.showQuick, true);
|
||||
@@ -135,8 +156,20 @@ export default {
|
||||
// 资料行总开关(面板「显示资料行」); 历史配置无此字段时默认显示, 兼容 1.0
|
||||
return flagOn(this.c.showRows, true);
|
||||
},
|
||||
showQr() {
|
||||
return flagOn(this.c.showQr, false);
|
||||
/** 二维码总条件: 开关 + 名片有二维码地址 */
|
||||
showQrBase() {
|
||||
return flagOn(this.c.showQr, false) && !!(this.card && this.card.qr_url);
|
||||
},
|
||||
qrPos() {
|
||||
return normalizeQrPosition(this.c.qrPosition);
|
||||
},
|
||||
showQrRows() { return this.showQrBase && this.qrPos === 'rows'; },
|
||||
showQrBottom() { return this.showQrBase && this.qrPos === 'bottom'; },
|
||||
showQrHead() { return this.showQrBase && this.qrPos === 'head'; },
|
||||
showQrCorner() { return this.showQrBase && this.qrPos === 'corner'; },
|
||||
qrImgStyle() {
|
||||
const s = QR_SIZES[this.qrPos];
|
||||
return this.$util.objToStyle({ width: s + 'rpx', height: s + 'rpx' });
|
||||
},
|
||||
showDetailBtn() {
|
||||
return flagOn(this.c.showDetailBtn, true);
|
||||
@@ -239,6 +272,18 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/** 资料行图标(开关关时退化为文字标签; field 无字形同样退化) */
|
||||
rowIcon(row) {
|
||||
return flagOn(this.c.showRowIcons, true) ? rowIconFor(row && row.field) : '';
|
||||
},
|
||||
/** 资料行是否可点: 行点击开关 + 服务端 href 派生得出动作 */
|
||||
rowTapable(row) {
|
||||
return flagOn(this.c.rowTap, true) && !!rowActOf(row);
|
||||
},
|
||||
onRowTap(row) {
|
||||
const act = rowActOf(row);
|
||||
if (act) this.$emit('row-act', act);
|
||||
},
|
||||
onAct(act) {
|
||||
this.$emit('act', act);
|
||||
},
|
||||
@@ -397,11 +442,24 @@ export default {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
/* 图标列与文字标签列同宽, 保证值列左缘对齐(参考图口径) */
|
||||
.row-icon {
|
||||
flex-shrink: 0;
|
||||
width: 96rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.row-value {
|
||||
flex: 1;
|
||||
font-size: 24rpx;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* 可点行: hover-class 已给按压反馈, 此处仅标识可点(光标语义小程序无效, 保留 H5) */
|
||||
&.row-tappable {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.row-toggle {
|
||||
@@ -455,6 +513,44 @@ export default {
|
||||
font-size: 22rpx;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
/* head 档: 头部第三段(头像|信息|二维码), 不占额外垂直节奏 */
|
||||
&.mini-qr-head {
|
||||
margin-top: 0;
|
||||
margin-left: 20rpx;
|
||||
flex-shrink: 0;
|
||||
align-self: center;
|
||||
|
||||
.qr-tip {
|
||||
font-size: 20rpx;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* corner 档: 右上角浮层, 白底衬底保证封面/纯色上都可扫 */
|
||||
&.mini-qr-corner {
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
right: 20rpx;
|
||||
z-index: 3;
|
||||
margin-top: 0;
|
||||
background: #ffffff;
|
||||
border-radius: 12rpx;
|
||||
padding: 10rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
}
|
||||
|
||||
/* corner 档: 浮层二维码(120rpx+衬底+tip)下探约 202rpx, 资料行/操作区让位防遮挡 */
|
||||
&.qr-at-corner {
|
||||
.mini-rows,
|
||||
.mini-acts {
|
||||
margin-top: 108rpx;
|
||||
}
|
||||
|
||||
.mini-head-info {
|
||||
max-width: calc(100% - 170rpx);
|
||||
}
|
||||
}
|
||||
|
||||
.mini-detail {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
:config="miniConfig"
|
||||
:loading="loading"
|
||||
@act="doAct"
|
||||
@row-act="doAct"
|
||||
@detail="goDetail"
|
||||
>
|
||||
<template v-if="isSelf" slot="badge">
|
||||
@@ -85,6 +86,10 @@ export default {
|
||||
maxRows: v.maxRows,
|
||||
showQr: v.showQr,
|
||||
qrTip: v.qrTip,
|
||||
// 3.0 A+B 视觉升级
|
||||
showRowIcons: v.showRowIcons,
|
||||
rowTap: v.rowTap,
|
||||
qrPosition: v.qrPosition,
|
||||
};
|
||||
},
|
||||
boxStyle() {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
:loading="loading"
|
||||
empty-tip="商家还未选择名片"
|
||||
@act="doAct"
|
||||
@row-act="doAct"
|
||||
@detail="goDetail"
|
||||
></diy-card-mini>
|
||||
</view>
|
||||
@@ -47,6 +48,10 @@ export default {
|
||||
maxRows: v.maxRows,
|
||||
showQr: v.showQr,
|
||||
qrTip: v.qrTip,
|
||||
// 3.0 A+B 视觉升级
|
||||
showRowIcons: v.showRowIcons,
|
||||
rowTap: v.rowTap,
|
||||
qrPosition: v.qrPosition,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
149
tests/diy-card-ab-visual.test.js
Normal file
149
tests/diy-card-ab-visual.test.js
Normal file
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* 名片 DIY 组件 · A+B 视觉升级契约测试
|
||||
*
|
||||
* 背景: 路径A(组件卡跳详情页) + 路径B(组件内复刻参考图视觉) 组合方案:
|
||||
* - 资料行图标(电话/微信/地址; 无字形字段退化文字标签, 与详情页同口径)
|
||||
* - 资料行可点(href 派生: 拨号/邮箱/复制)
|
||||
* - 二维码四档任意位(rows 行下 / head 头部右侧 / corner 右上浮层 / bottom 主按钮上)
|
||||
* - corner 档 Logo 自动避让 + 资料行让位(防遮挡)
|
||||
*
|
||||
* 覆盖:
|
||||
* 1. common.js 纯函数(动态 import 真值断言, 非正则)
|
||||
* 2. mini.vue 模板/计算/方法契约(先剥注释再断言)
|
||||
* 3. show/self 透传 + @row-act 接线
|
||||
* 4. 反例: 虚构字段/不存在的档位必须查不到
|
||||
*
|
||||
* 跑法: node --test tests/diy-card-ab-visual.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 showSrc = strip(read('diy-card-show.vue'));
|
||||
const selfSrc = strip(read('diy-card-self.vue'));
|
||||
const miniSrc = strip(read('diy-card-mini.vue'));
|
||||
|
||||
// =============================================================
|
||||
// 1. common.js 纯函数(真值断言)
|
||||
// =============================================================
|
||||
|
||||
test('common: rowIconFor 字段→图标映射(无字形字段退化为空)', async () => {
|
||||
const m = await import('../components-diy/diy-card-common.js');
|
||||
assert.equal(m.rowIconFor('mobile'), 'icon-dianhua');
|
||||
assert.equal(m.rowIconFor('phone'), 'icon-dianhua');
|
||||
assert.equal(m.rowIconFor('wechat'), 'icon-weixin');
|
||||
assert.equal(m.rowIconFor('address'), 'icon-dizhi');
|
||||
// 无字形: 邮箱/网站/自定义/未知 → 空串(模板退化为文字标签)
|
||||
assert.equal(m.rowIconFor('email'), '');
|
||||
assert.equal(m.rowIconFor('website'), '');
|
||||
assert.equal(m.rowIconFor('custom'), '');
|
||||
assert.equal(m.rowIconFor(''), '');
|
||||
assert.equal(m.rowIconFor(undefined), '');
|
||||
});
|
||||
|
||||
test('common: rowActOf 由 href 派生点击动作', async () => {
|
||||
const m = await import('../components-diy/diy-card-common.js');
|
||||
assert.deepEqual(m.rowActOf({ field: 'phone', value: '22644212509', href: 'tel:22644212509' }), { type: 'tel', value: '22644212509' });
|
||||
assert.deepEqual(m.rowActOf({ field: 'email', value: 'a@b.com', href: 'mailto:a@b.com' }), { type: 'mail', value: 'a@b.com' });
|
||||
assert.deepEqual(m.rowActOf({ field: 'website', value: 'https://x.com', href: 'https://x.com' }), { type: 'copy', value: 'https://x.com' });
|
||||
// 空 href(地址/自定义)或空值 → 不可点
|
||||
assert.equal(m.rowActOf({ field: 'address', value: '上海', href: '' }), null);
|
||||
assert.equal(m.rowActOf({ field: 'phone', value: '', href: 'tel:' }), null);
|
||||
assert.equal(m.rowActOf(null), null);
|
||||
});
|
||||
|
||||
test('common: 二维码四档归一化 + 尺寸表', async () => {
|
||||
const m = await import('../components-diy/diy-card-common.js');
|
||||
for (const p of ['rows', 'head', 'corner', 'bottom']) {
|
||||
assert.equal(m.normalizeQrPosition(p), p);
|
||||
}
|
||||
// 未配置/非法值 → rows(保守默认, 不炸)
|
||||
assert.equal(m.normalizeQrPosition(''), 'rows');
|
||||
assert.equal(m.normalizeQrPosition('sideways'), 'rows');
|
||||
assert.equal(m.normalizeQrPosition(undefined), 'rows');
|
||||
assert.deepEqual(Object.keys(m.QR_SIZES).sort(), ['bottom', 'corner', 'head', 'rows']);
|
||||
});
|
||||
|
||||
// =============================================================
|
||||
// 2. mini.vue 基件契约
|
||||
// =============================================================
|
||||
|
||||
test('mini: 资料行图标列 + 文字标签退化', () => {
|
||||
assert.match(miniSrc, /<text v-if="rowIcon\(row\)" class="row-icon iconfont" :class="rowIcon\(row\)"/);
|
||||
assert.match(miniSrc, /<text v-else class="row-label"/);
|
||||
// rowIcon 受 showRowIcons 开关控制(默认开)
|
||||
assert.match(miniSrc, /rowIcon\(row\)\s*\{\s*return flagOn\(this\.c\.showRowIcons,\s*true\)/);
|
||||
});
|
||||
|
||||
test('mini: 资料行可点(开关 + href 派生 + emit row-act)', () => {
|
||||
assert.match(miniSrc, /rowTapable\(row\)\s*\{\s*return flagOn\(this\.c\.rowTap,\s*true\)\s*&&\s*!!rowActOf\(row\)/);
|
||||
assert.match(miniSrc, /:class="\{ 'row-tappable': rowTapable\(row\) \}"/);
|
||||
assert.match(miniSrc, /@tap="onRowTap\(row\)"/);
|
||||
assert.match(miniSrc, /onRowTap\(row\)\s*\{[\s\S]*?this\.\$emit\('row-act',\s*act\)/);
|
||||
});
|
||||
|
||||
test('mini: 二维码四档渲染分支齐全', () => {
|
||||
assert.match(miniSrc, /showQrRows\(\)\s*\{\s*return this\.showQrBase && this\.qrPos === 'rows'/);
|
||||
assert.match(miniSrc, /showQrBottom\(\)\s*\{\s*return this\.showQrBase && this\.qrPos === 'bottom'/);
|
||||
assert.match(miniSrc, /showQrHead\(\)\s*\{\s*return this\.showQrBase && this\.qrPos === 'head'/);
|
||||
assert.match(miniSrc, /showQrCorner\(\)\s*\{\s*return this\.showQrBase && this\.qrPos === 'corner'/);
|
||||
// 模板四个挂点
|
||||
assert.match(miniSrc, /v-if="showQrCorner" class="mini-qr mini-qr-corner"/);
|
||||
assert.match(miniSrc, /v-if="showQrHead" class="mini-qr mini-qr-head"/);
|
||||
assert.match(miniSrc, /v-if="showQrRows" class="mini-qr"/);
|
||||
assert.match(miniSrc, /v-if="showQrBottom" class="mini-qr"/);
|
||||
// 总条件仍要求名片有 qr_url
|
||||
assert.match(miniSrc, /showQrBase\(\)\s*\{\s*return flagOn\(this\.c\.showQr,\s*false\) && !!\(this\.card && this\.card\.qr_url\)/);
|
||||
});
|
||||
|
||||
test('mini: corner 档 Logo 避让 + 资料行让位样式', () => {
|
||||
assert.match(miniSrc, /showLogo\(\)\s*\{[\s\S]*?this\.qrPos !== 'corner'/);
|
||||
assert.match(miniSrc, /:class="\{ 'qr-at-corner': qrPos === 'corner' \}"/);
|
||||
assert.match(miniSrc, /&\.qr-at-corner \{[\s\S]*?\.mini-rows,\s*\.mini-acts\s*\{\s*margin-top: 108rpx;/);
|
||||
// 浮层衬底(封面/纯色上都可扫)
|
||||
assert.match(miniSrc, /&\.mini-qr-corner \{[\s\S]*?background: #ffffff;/);
|
||||
});
|
||||
|
||||
// =============================================================
|
||||
// 3. show/self 透传 + 接线
|
||||
// =============================================================
|
||||
|
||||
for (const [name, src] of [['show', showSrc], ['self', selfSrc]]) {
|
||||
test(name + ': miniConfig 透传 A+B 三字段', () => {
|
||||
assert.match(src, /showRowIcons:\s*v\.showRowIcons/);
|
||||
assert.match(src, /rowTap:\s*v\.rowTap/);
|
||||
assert.match(src, /qrPosition:\s*v\.qrPosition/);
|
||||
});
|
||||
test(name + ': 模板挂 @row-act="doAct"(行点击有接收方)', () => {
|
||||
assert.match(src, /@row-act="doAct"/);
|
||||
});
|
||||
}
|
||||
|
||||
// =============================================================
|
||||
// 反例(防恒真)
|
||||
// =============================================================
|
||||
|
||||
test('反例: 不存在第五档 qrPosition 分支', () => {
|
||||
assert.ok(!miniSrc.includes("qrPos === 'sideways'"));
|
||||
assert.ok(!miniSrc.includes('showQrSideways'));
|
||||
});
|
||||
|
||||
test('反例: 行图标不映射不存在的字段', async () => {
|
||||
const m = await import('../components-diy/diy-card-common.js');
|
||||
assert.equal(m.rowIconFor('nosuchfield'), '');
|
||||
// 未知 scheme 的 href(后端实际只会下发 tel:/mailto:/安全 URL/空) → 复制兜底
|
||||
assert.deepEqual(m.rowActOf({ field: 'nosuchfield', value: 'x', href: 'nosuch:x' }), { type: 'copy', value: 'x' });
|
||||
// 真空 href 才不可点
|
||||
assert.equal(m.rowActOf({ field: 'nosuchfield', value: 'x', href: '' }), null);
|
||||
});
|
||||
Reference in New Issue
Block a user