/** * 名片 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(//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')); });