148 lines
7.2 KiB
JavaScript
148 lines
7.2 KiB
JavaScript
/**
|
|
* 名片 DIY 组件 · 路径A(组件卡 → 跳完整名片页)链路测试
|
|
*
|
|
* 背景:
|
|
* 商家端装修页嵌入名片组件后, C 端(微页面/小程序)渲染的是组件内迷你卡。
|
|
* 参考图形态(名片卡 + 二维码 + 全宽「发名片」)的完整功能不在组件里, 而在
|
|
* 现成的会员端详情页 /pages_tool/card/detail —— 组件靠「查看完整名片」
|
|
* 全宽主按钮把用户送过去(路径A)。游客未登录时 detail.vue 自动
|
|
* fallbackToPublicPage 落地 wap 公开页(保存通讯录/分享/二维码), 不是死路。
|
|
*
|
|
* 本测试把这条链路的源码契约固化为断言, 防回归:
|
|
* 1. self 组件 miniConfig 透传 showDetailBtn + detailBtnText; show 组件自绘
|
|
* 二维码块直挂 goDetail(2026-10-21 换思路: show 不再复用 mini 基件)
|
|
* 2. 两个组件模板都挂 @detail="goDetail"(基件 emit('detail') 有接收方)
|
|
* 3. goDetail 跳 /pages_tool/card/detail 且带 card_id 参数
|
|
* 4. mini 基件: showDetailBtn flagOn 默认开(开箱即用) + 全宽按钮 emit detail
|
|
* 5. detail.vue 游客兜底: 错误信封带 wap_url → fallbackToPublicPage(死路防护)
|
|
*
|
|
* 口径: 断言前先剔除注释(HTML/块/行注释), 防止「注释里写了某写法」被算实现
|
|
* (本项目踩过: 注释里写「绝不能写 X」把自己判违规)。
|
|
* 反例: 虚构选择器/不存在的字段必须查不到, 证明断言不是恒真。
|
|
*
|
|
* 跑法: node --test tests/diy-card-detail-jump.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 CARD_DIR = path.resolve(__dirname, '../pages_tool/card');
|
|
const read = f => fs.readFileSync(path.join(DIY_DIR, f), 'utf8');
|
|
const readCard = f => fs.readFileSync(path.join(CARD_DIR, f), 'utf8');
|
|
|
|
/** 剔除三类注释: HTML <!-- --> / 块 /* *\/ / 行 // (保护 https:// 里的 //) */
|
|
function stripComments(src) {
|
|
return src
|
|
.replace(/<!--[\s\S]*?-->/g, '')
|
|
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
.replace(/(^|[^:])\/\/.*$/gm, '$1');
|
|
}
|
|
|
|
const showSrc = stripComments(read('diy-card-show.vue'));
|
|
const selfSrc = stripComments(read('diy-card-self.vue'));
|
|
const miniSrc = stripComments(read('diy-card-mini.vue'));
|
|
const detailSrc = stripComments(readCard('detail.vue'));
|
|
|
|
// =============================================================
|
|
// 1. 详情页入口(self 走 mini 基件透传; show 自绘二维码直点进详情)
|
|
// =============================================================
|
|
|
|
test('self: miniConfig 透传 showDetailBtn 与 detailBtnText', () => {
|
|
assert.match(selfSrc, /showDetailBtn:\s*v\.showDetailBtn/);
|
|
assert.match(selfSrc, /detailBtnText:\s*v\.detailBtnText/);
|
|
});
|
|
|
|
test('show: 自绘二维码块挂 @tap.stop="openCardShare"(点二维码弹「分享名片」抽屉)', () => {
|
|
assert.match(showSrc, /@tap\.stop="openCardShare"/);
|
|
});
|
|
|
|
// =============================================================
|
|
// 2. 模板挂 @detail(基件 emit('detail') 的接收方, 断链则按钮变哑巴)
|
|
// =============================================================
|
|
|
|
test('self: 模板挂 @detail="goDetail"', () => {
|
|
assert.match(selfSrc, /@detail="goDetail"/);
|
|
});
|
|
|
|
// =============================================================
|
|
// 3. goDetail 跳会员端详情页且带 card_id(路径A落点)
|
|
// =============================================================
|
|
|
|
test('show: goDetail 跳 /pages_tool/card/detail 并带 card_id', () => {
|
|
assert.match(showSrc, /redirectTo\('\/pages_tool\/card\/detail'/);
|
|
assert.match(showSrc, /card_id:\s*card\.card_id/);
|
|
// 守卫: 无 card_id 不跳(防脏数据跳空页)
|
|
assert.match(showSrc, /if\s*\(!card\s*\|\|\s*!card\.card_id\)\s*return;/);
|
|
});
|
|
|
|
test('self: goDetail 跳 /pages_tool/card/detail 并带 card_id', () => {
|
|
assert.match(selfSrc, /redirectTo\('\/pages_tool\/card\/detail'/);
|
|
assert.match(selfSrc, /card_id:\s*card\.card_id/);
|
|
assert.match(selfSrc, /if\s*\(!card\s*\|\|\s*!card\.card_id\)\s*return;/);
|
|
});
|
|
|
|
// =============================================================
|
|
// 4. mini 基件: 默认开 + 全宽按钮 + emit(路径A的渲染与出口)
|
|
// =============================================================
|
|
|
|
test('mini: showDetailBtn 默认开(历史配置无此字段时也显示, 开箱即用)', () => {
|
|
assert.match(miniSrc, /flagOn\(this\.c\.showDetailBtn,\s*true\)/);
|
|
});
|
|
|
|
test('mini: 发送名片按钮(小程序原生转发 + H5 弹抽屉, 双分支)', () => {
|
|
assert.match(miniSrc, /class="mini-detail"/);
|
|
// 小程序端: <button open-type="share"> 直接拉起「发送给朋友」面板(与微信分享一致)
|
|
assert.match(miniSrc, /open-type="share"/);
|
|
assert.match(miniSrc, /:data-card-id="card && card\.card_id"/);
|
|
assert.match(miniSrc, /:data-card-name="card && card\.name"/);
|
|
assert.match(miniSrc, /:data-card-qr="card && card\.qr_url"/);
|
|
// H5 端: 仍弹「分享名片」抽屉(原生分享在 H5 不可用)
|
|
assert.match(miniSrc, /@tap\.stop="openCardShare"/);
|
|
assert.match(miniSrc, /openCardShare\(\)\s*\{\s*this\.showShare = true;/);
|
|
// 抽屉版式与 show 同款: 标题/副文案/二维码/复制链接
|
|
assert.match(miniSrc, /<view class="ss-title">分享名片<\/view>/);
|
|
assert.match(miniSrc, /客户打开链接即可查看名片并留言留资/);
|
|
assert.match(miniSrc, /class="ss-btn ss-btn-main" :style="shareBtnStyle" @tap="copyCardLink"/);
|
|
// 遮罩关闭 + H5 分享/小程序存码方法齐备
|
|
assert.match(miniSrc, /class="cs-mask" v-if="showShare" @tap="closeShare"/);
|
|
assert.match(miniSrc, /h5CardShare\(\)/);
|
|
assert.match(miniSrc, /saveCardQr\(\)/);
|
|
});
|
|
|
|
test('mini: 主按钮文案可配(detailBtnText, 面板改字即生效)', () => {
|
|
assert.match(miniSrc, /detailBtnText\(\)\s*\{[\s\S]*?this\.c\.detailBtnText/);
|
|
});
|
|
|
|
// =============================================================
|
|
// 5. detail.vue 游客兜底(路径A对游客成立的关键: 不死路)
|
|
// =============================================================
|
|
|
|
test('detail: 错误信封带 wap_url 时 fallbackToPublicPage 落地公开页', () => {
|
|
assert.match(detailSrc, /if\s*\(data\.wap_url\)\s*\{\s*this\.fallbackToPublicPage\(data\.wap_url\);/);
|
|
});
|
|
|
|
test('detail: fallbackToPublicPage 双端分支(H5 直跳/非 H5 走 share 中转 web-view)', () => {
|
|
assert.match(detailSrc, /fallbackToPublicPage\(wapUrl\)\s*\{/);
|
|
assert.match(detailSrc, /window\.location\.href\s*=\s*wapUrl/);
|
|
assert.match(detailSrc, /'\/pages_tool\/card\/share\?url='\s*\+\s*encodeURIComponent\(wapUrl\)/);
|
|
});
|
|
|
|
// =============================================================
|
|
// 反例自测(防恒真): 虚构字段/错误落点必须不存在
|
|
// =============================================================
|
|
|
|
test('反例: 两个组件都不存在虚构字段 nc.__no_such_field__', () => {
|
|
assert.ok(!showSrc.includes('__no_such_field__'));
|
|
assert.ok(!selfSrc.includes('__no_such_field__'));
|
|
});
|
|
|
|
test('反例: goDetail 不跳不存在的页面', () => {
|
|
assert.ok(!showSrc.includes('/pages_tool/card/nosuchpage'));
|
|
assert.ok(!selfSrc.includes('/pages_tool/card/nosuchpage'));
|
|
});
|