197 行
6.8 KiB
TypeScript
197 行
6.8 KiB
TypeScript
import test from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import fs from 'node:fs';
|
||
import path from 'node:path';
|
||
|
||
const MP_ROOT = path.resolve('m:/2026项目/CRMmp/mpuniapp');
|
||
const portalUtsPath = path.join(MP_ROOT, 'pages/portal/usePortal.uts');
|
||
const portalUvuePath = path.join(MP_ROOT, 'pages/portal/index.uvue');
|
||
const cardHomeUtsPath = path.join(MP_ROOT, 'pages/owner/card/useCardHome.uts');
|
||
|
||
test('1. usePortal.uts 文件必须存在且导出主客态核心函数', () => {
|
||
assert.ok(fs.existsSync(portalUtsPath), '必须存在 usePortal.uts');
|
||
const content = fs.readFileSync(portalUtsPath, 'utf-8');
|
||
assert.ok(content.includes('export function checkIsCardOwner'), '必须导出 checkIsCardOwner');
|
||
assert.ok(content.includes('export function resolvePortalCtaText'), '必须导出 resolvePortalCtaText');
|
||
assert.ok(content.includes('export function resolvePortalHeaderLinkText'), '必须导出 resolvePortalHeaderLinkText');
|
||
assert.ok(content.includes('export function getOwnerCardIds'), '必须导出 getOwnerCardIds');
|
||
assert.ok(content.includes('export function setOwnerCardIds'), '必须导出 setOwnerCardIds');
|
||
});
|
||
|
||
test('2. 主客态精准判定算法逻辑正确性 (checkIsCardOwner)', () => {
|
||
// 模拟纯逻辑实现契约测试
|
||
type CheckParams = {
|
||
hasToken: boolean;
|
||
queryId?: string;
|
||
queryMode?: string;
|
||
cardId?: string | number;
|
||
cardSlug?: string;
|
||
ownerCardIds?: string[];
|
||
};
|
||
|
||
function simulateCheckIsCardOwner(p: CheckParams): boolean {
|
||
if (p.queryMode === 'owner') {
|
||
return true;
|
||
}
|
||
if (!p.hasToken) {
|
||
return false;
|
||
}
|
||
const qId = (p.queryId || '').trim().toLowerCase();
|
||
if (qId === '' || qId === 'default') {
|
||
return true;
|
||
}
|
||
const currentId = p.cardId != null ? String(p.cardId).trim() : '';
|
||
const currentSlug = p.cardSlug != null ? String(p.cardSlug).trim() : '';
|
||
const owners = p.ownerCardIds || [];
|
||
for (let i = 0; i < owners.length; i++) {
|
||
const oid = String(owners[i]).trim();
|
||
if (oid !== '' && (oid === currentId || oid === currentSlug)) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 场景 A:外部访客未登录访问任意名片 -> false
|
||
assert.equal(
|
||
simulateCheckIsCardOwner({ hasToken: false, queryId: '1001', cardId: 1001 }),
|
||
false,
|
||
'外部未登录访客必须为访客态'
|
||
);
|
||
|
||
// 场景 B:已登录用户从 App TabBar 默认进入 ('default') -> true
|
||
assert.equal(
|
||
simulateCheckIsCardOwner({ hasToken: true, queryId: 'default', cardId: 1001 }),
|
||
true,
|
||
'已登录用户默认访问自身名片必须为主人态'
|
||
);
|
||
|
||
// 场景 C:携带 mode=owner 参数预览 -> true
|
||
assert.equal(
|
||
simulateCheckIsCardOwner({ hasToken: false, queryMode: 'owner', cardId: 2002 }),
|
||
true,
|
||
'显式声明 mode=owner 必须为主人态'
|
||
);
|
||
|
||
// 场景 D:已登录用户访问属于自己的名片 ID -> true
|
||
assert.equal(
|
||
simulateCheckIsCardOwner({
|
||
hasToken: true,
|
||
queryId: '1005',
|
||
cardId: 1005,
|
||
ownerCardIds: ['1001', '1005', 'alex-morgan'],
|
||
}),
|
||
true,
|
||
'访问自身名片库中的 ID 必须为主人态'
|
||
);
|
||
|
||
// 场景 E:已登录用户访问他人的名片 -> false
|
||
assert.equal(
|
||
simulateCheckIsCardOwner({
|
||
hasToken: true,
|
||
queryId: '9999',
|
||
cardId: 9999,
|
||
ownerCardIds: ['1001', '1005'],
|
||
}),
|
||
false,
|
||
'已登录用户访问他人名片必须为访客态'
|
||
);
|
||
});
|
||
|
||
test('3. index.uvue 视图层主客态自适应规范审查', () => {
|
||
assert.ok(fs.existsSync(portalUvuePath), '必须存在 index.uvue');
|
||
const content = fs.readFileSync(portalUvuePath, 'utf-8');
|
||
|
||
// 必须引入 checkIsCardOwner
|
||
assert.ok(
|
||
content.includes('checkIsCardOwner'),
|
||
'index.uvue 必须调用 checkIsCardOwner'
|
||
);
|
||
|
||
// 必须具备 isOwner 响应式状态或计算属性
|
||
assert.ok(
|
||
content.includes('isOwner'),
|
||
'index.uvue 必须具备 isOwner 判断'
|
||
);
|
||
|
||
// 主 CTA 在主人态下调用分享/海报逻辑,访客态调用 saveToContacts
|
||
assert.ok(
|
||
content.includes('handlePrimaryCta') || content.includes('handleSaveContact'),
|
||
'index.uvue 必须包含主 CTA 统一处理函数'
|
||
);
|
||
|
||
// 主人态必须可直接导航至二维码/海报页
|
||
assert.ok(
|
||
content.includes('/pages/owner/card/qrcode'),
|
||
'index.uvue 主人态必须支持跳转至海报/展台页'
|
||
);
|
||
|
||
// 主人态顶栏链接必须支持直接编辑微官网
|
||
assert.ok(
|
||
content.includes('/pages/owner/card/edit'),
|
||
'index.uvue 主人态顶栏必须支持跳转至编辑微官网页'
|
||
);
|
||
|
||
// 右上角收入大厅必须在主人态下受控隐藏
|
||
assert.ok(
|
||
content.includes('canCollect') || content.includes('!isOwner'),
|
||
'index.uvue 右上角收入大厅必须在主人态下隐藏'
|
||
);
|
||
});
|
||
|
||
test('4. useCardHome.uts 必须在名片加载时持久化名片主人 ID 集合', () => {
|
||
assert.ok(fs.existsSync(cardHomeUtsPath), '必须存在 useCardHome.uts');
|
||
const content = fs.readFileSync(cardHomeUtsPath, 'utf-8');
|
||
assert.ok(
|
||
content.includes('setOwnerCardIds') || content.includes('omni_owner_card_ids'),
|
||
'useCardHome.uts 必须持久化名片主人 ID 集合'
|
||
);
|
||
});
|
||
|
||
test('5. 多语言文案解析与主客态映射审查', () => {
|
||
const portalContent = fs.readFileSync(portalUtsPath, 'utf-8');
|
||
assert.ok(
|
||
portalContent.includes("getOmniText(loc, 'card.share')"),
|
||
'主人态主 CTA 必须映射 card.share 词条'
|
||
);
|
||
assert.ok(
|
||
portalContent.includes("getOmniText(loc, 'card.save_contact')"),
|
||
'访客态主 CTA 必须映射 card.save_contact 词条'
|
||
);
|
||
assert.ok(
|
||
portalContent.includes("getOmniText(loc, 'common.edit')"),
|
||
'主人态顶栏操作必须映射 common.edit 词条'
|
||
);
|
||
assert.ok(
|
||
portalContent.includes("getOmniText(loc, 'card.exchange')"),
|
||
'访客态顶栏操作必须映射 card.exchange 词条'
|
||
);
|
||
});
|
||
|
||
test('6. 主 CTA 靠右侧图标形式与悬浮触控规范审查', () => {
|
||
const content = fs.readFileSync(portalUvuePath, 'utf-8');
|
||
assert.ok(
|
||
content.includes('inset-inline-end: 16px') || content.includes('right: 16px'),
|
||
'主 CTA 必须靠右侧布局 (inset-inline-end: 16px)'
|
||
);
|
||
assert.ok(
|
||
content.includes('border-radius: 50%') ||
|
||
content.includes('border-radius: var(--oc-radius-pill, 9999px);') ||
|
||
content.includes('border-radius: 9999px;'),
|
||
'主 CTA 必须使用圆形或大圆角图标形态'
|
||
);
|
||
assert.ok(
|
||
content.includes('height: 48px') || content.includes('min-height: 44px'),
|
||
'主 CTA 触控尺寸必须满足规范 (>= 44px)'
|
||
);
|
||
assert.ok(
|
||
content.includes('env(safe-area-inset-bottom') && content.includes('var(--window-bottom'),
|
||
'主 CTA 必须适配视口安全区与 TabBar 避让'
|
||
);
|
||
assert.ok(
|
||
content.includes('box-shadow: none;'),
|
||
'主 CTA 边缘严禁携带弥散阴影,保证纯净平滑边缘'
|
||
);
|
||
});
|
||
|