- common.js: ACT_GRID_ICONS 结构化{glyph|css} + save 紫档 + WAP_LINE_ICONS(SVG 线稿
data URI, 自写 b64ascii) + CARD_ICON_LIB 素材库 30 枚(与后端三源同步) +
normalizeActVis/ActLabels/CustomActs/ActOrder + libIconBg 实时上色
- mini.vue: 宫格图标优先级 img > 素材库 > SVG 线稿 > 首字; acts computed 重构
(actVis 显隐过滤 + actLabels 改名 + actOrder 次序 + order 外扩展类型尾接 +
customActs 尾接 + 四 cfg 字段合并); 操作区双位置 + 独立成块样式 + qrSrc 自定义图
- show/self.vue: doAct 补 save 分支(goSaveContact 资料白名单下发) + miniConfig
透传 actVis/actLabels/actOrder/customActs/qrImage 等 2.17~2.19 字段
- 测试: diy-card 7 套件 111 项全绿(style-v2 22 项含 svg 快照/素材库三源同步/
template 配平/嵌套结构反例)
140 lines
5.5 KiB
JavaScript
140 lines
5.5 KiB
JavaScript
/**
|
|
* 名片 DIY 组件 · 跳转目标(含平台统一「系统链接」)链路测试
|
|
*
|
|
* 背景:
|
|
* 商家在设计面板给「名片入口横幅 / 团队名片墙」配跳转目标, 有四档:
|
|
* list → 名片夹列表(内置)
|
|
* contacts→ 我的联系人(内置)
|
|
* custom → 自定义站内路径
|
|
* link → 平台统一「系统链接」(商品/分类/微页面/小程序/拨号/自定义外链…)
|
|
* 第四档由后台的 nc-link 组件写入一个 link 对象
|
|
* {name,title,wap_url,page_key,page_params}, C 端必须走 $util.diyRedirectTo
|
|
* 交给平台统一处理(内部页/外链/小程序/拨号), 不能自己拼 URL。
|
|
*
|
|
* 覆盖:
|
|
* 1. entry/list 的 link 模式走 diyRedirectTo(而不是 redirectTo)
|
|
* 2. 未选链接时有守卫(不跳空)
|
|
* 3. 内置三档仍走 redirectTo(没被新分支吃掉)
|
|
* 4. 反例: 不存在虚构模式/虚构字段
|
|
*
|
|
* 跑法: node --test tests/diy-card-link-target.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');
|
|
|
|
/** 剔除三类注释: HTML <!-- --> / 块 /* *\/ / 行 // (保护 https:// 里的 //) */
|
|
function stripComments(src) {
|
|
return src
|
|
.replace(/<!--[\s\S]*?-->/g, '')
|
|
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
.replace(/(^|[^:])\/\/.*$/gm, '$1');
|
|
}
|
|
|
|
const entrySrc = stripComments(read('diy-card-entry.vue'));
|
|
const listSrc = stripComments(read('diy-card-list.vue'));
|
|
|
|
/** 取某个方法体(从方法名到下一个同级方法定义) */
|
|
function methodBody(src, name) {
|
|
const start = src.indexOf(name + '(');
|
|
if (start === -1) return '';
|
|
// 从 '{' 开始做括号配平
|
|
let i = src.indexOf('{', start);
|
|
if (i === -1) return '';
|
|
let depth = 0;
|
|
for (let j = i; j < src.length; j++) {
|
|
if (src[j] === '{') depth++;
|
|
else if (src[j] === '}') {
|
|
depth--;
|
|
if (depth === 0) return src.slice(start, j + 1);
|
|
}
|
|
}
|
|
return src.slice(start);
|
|
}
|
|
|
|
const entryGoLink = methodBody(entrySrc, 'goLink');
|
|
const listGoMore = methodBody(listSrc, 'goMore');
|
|
|
|
// =============================================================
|
|
// 1. link 模式走平台统一跳转
|
|
// =============================================================
|
|
|
|
test('entry: goLink 的 link 模式走 $util.diyRedirectTo(平台统一处理链接对象)', () => {
|
|
assert.match(entryGoLink, /linkMode\s*===\s*'link'/);
|
|
assert.match(entryGoLink, /this\.\$util\.diyRedirectTo\(link\)/);
|
|
// link 模式必须**提前 return**, 不能落到下面的 redirectTo(url)
|
|
const linkBranch = entryGoLink.slice(entryGoLink.indexOf("linkMode === 'link'"));
|
|
assert.match(linkBranch.slice(0, 400), /return;/);
|
|
});
|
|
|
|
test('list: goMore 的 link 模式走 $util.diyRedirectTo', () => {
|
|
// list 的写法是先归一 mode = String(v.moreLinkMode || 'list'), 再按 mode 分支
|
|
assert.match(listGoMore, /const\s+mode\s*=\s*String\(this\.v\.moreLinkMode\s*\|\|\s*'list'\)/);
|
|
assert.match(listGoMore, /mode\s*===\s*'link'/);
|
|
assert.match(listGoMore, /this\.\$util\.diyRedirectTo\(link\)/);
|
|
const linkBranch = listGoMore.slice(listGoMore.indexOf("mode === 'link'"));
|
|
assert.match(linkBranch.slice(0, 400), /return;/);
|
|
});
|
|
|
|
test('entry: link 取的是配置里的 link 对象(不是 wap_url 字符串)', () => {
|
|
assert.match(entryGoLink, /v\.link\s*&&\s*typeof\s+v\.link\s*===\s*'object'/);
|
|
});
|
|
|
|
test('list: link 取的是配置里的 moreLink 对象', () => {
|
|
assert.match(listGoMore, /this\.v\.moreLink\s*&&\s*typeof\s+this\.v\.moreLink\s*===\s*'object'/);
|
|
});
|
|
|
|
// =============================================================
|
|
// 2. 未选链接的守卫(不跳空)
|
|
// =============================================================
|
|
|
|
test('entry: 未选链接时提示而不是跳空', () => {
|
|
assert.match(entryGoLink, /if\s*\(!link\s*\|\|\s*!link\.name\)/);
|
|
assert.match(entryGoLink, /请先选择链接/);
|
|
});
|
|
|
|
test('list: 未选链接时提示而不是跳空', () => {
|
|
assert.match(listGoMore, /if\s*\(!link\s*\|\|\s*!link\.name\)/);
|
|
assert.match(listGoMore, /请先选择链接/);
|
|
});
|
|
|
|
// =============================================================
|
|
// 3. 内置三档仍走 redirectTo(没被新分支吃掉)
|
|
// =============================================================
|
|
|
|
test('entry: 内置三档仍是 redirectTo(名片夹/联系人/自定义路径)', () => {
|
|
assert.match(entryGoLink, /linkMode\s*===\s*'contacts'/);
|
|
assert.match(entryGoLink, /\/pages_tool\/card\/contacts/);
|
|
assert.match(entryGoLink, /linkMode\s*===\s*'custom'/);
|
|
assert.match(entryGoLink, /\/pages_tool\/card\/list/);
|
|
assert.match(entryGoLink, /this\.\$util\.redirectTo\(url\)/);
|
|
});
|
|
|
|
test('list: 内置三档仍是 redirectTo', () => {
|
|
assert.match(listGoMore, /mode\s*===\s*'contacts'/);
|
|
assert.match(listGoMore, /\/pages_tool\/card\/contacts/);
|
|
assert.match(listGoMore, /mode\s*===\s*'custom'/);
|
|
assert.match(listGoMore, /this\.\$util\.redirectTo\(url\)/);
|
|
});
|
|
|
|
// =============================================================
|
|
// 反例自测(防恒真): 虚构模式/虚构字段必须不存在
|
|
// =============================================================
|
|
|
|
test('反例: 两个组件都不存在虚构跳转模式', () => {
|
|
assert.ok(!entrySrc.includes("linkMode === 'nosuchmode'"));
|
|
assert.ok(!listSrc.includes("moreLinkMode === 'nosuchmode'"));
|
|
});
|
|
|
|
test('反例: 两个组件都不存在虚构链接字段', () => {
|
|
assert.ok(!entrySrc.includes('v.linkMagic'));
|
|
assert.ok(!listSrc.includes('v.moreLinkMagic'));
|
|
});
|