187 lines
8.5 KiB
JavaScript
187 lines
8.5 KiB
JavaScript
/**
|
|
* 名片云 · 商家端(admin)页面测试
|
|
*
|
|
* 背景:
|
|
* 商家端 7 个页面中, 后台接口先就绪的是 contact/* 与 lead/*(以及 card/lists),
|
|
* 因此本轮先上三个页面(其余 5 页等后台接口, 见 CARD_API_REFERENCE.md 缺口):
|
|
* - admin/manage 名片管理(基础版: 搜索/详情/编辑, 详情编辑复用员工端页)
|
|
* - admin/contacts 人脉管理(列表/搜索/来源筛选/编辑/转线索/删除)
|
|
* - admin/leads 潜在客户(状态筛选/列表/更新跟进状态)
|
|
* 本测试固化商家端契约: 注册/收口/鸿蒙安全导航/复用回跳。
|
|
*
|
|
* 跑法:
|
|
* node --test tests/card-admin.test.js
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const ROOT = path.resolve(__dirname, '..');
|
|
const ADMIN_DIR = path.join(ROOT, 'pages_tool/card/admin');
|
|
const readAdmin = f => fs.readFileSync(path.join(ADMIN_DIR, f), 'utf8');
|
|
const readCard = f => fs.readFileSync(path.join(ROOT, 'pages_tool/card', f), 'utf8');
|
|
const pagesSrc = fs.readFileSync(path.join(ROOT, 'pages.json'), 'utf8');
|
|
const utilSrc = fs.readFileSync(path.join(ROOT, 'common/js/util.js'), 'utf8');
|
|
|
|
// pages.json 含 uni-app 合法 // 注释, 剥后解析
|
|
const stripped = pagesSrc.replace(/^\s*\/\/.*$/gm, '').replace(/,(\s*[}\]])/g, '$1');
|
|
const pagesJson = JSON.parse(stripped);
|
|
|
|
const ADMIN_PAGES = {
|
|
'card/admin/manage': '名片管理',
|
|
'card/admin/contacts': '人脉管理',
|
|
'card/admin/leads': '潜在客户'
|
|
};
|
|
|
|
/** 取 pages_tool 分包已注册路径 */
|
|
function toolPaths() {
|
|
const sub = (pagesJson.subPackages || []).find(sp => sp.root === 'pages_tool');
|
|
assert.ok(sub, 'pages_tool 分包应存在');
|
|
return (sub.pages || []).map(p => p.path);
|
|
}
|
|
|
|
// =============================================================
|
|
// 1. 页面注册
|
|
// =============================================================
|
|
|
|
test('注册: 商家端 3 页已注册进 pages.json 的 pages_tool 分包', () => {
|
|
const paths = toolPaths();
|
|
for (const p of Object.keys(ADMIN_PAGES)) {
|
|
assert.ok(paths.includes(p), `${p} 未注册: ${paths.join(',')}`);
|
|
}
|
|
});
|
|
|
|
test('注册: 商家端页面标题正确且不在 TabBar', () => {
|
|
const sub = (pagesJson.subPackages || []).find(sp => sp.root === 'pages_tool');
|
|
for (const [p, title] of Object.entries(ADMIN_PAGES)) {
|
|
const page = (sub.pages || []).find(x => x.path === p);
|
|
assert.ok(page, `${p} 应存在`);
|
|
assert.equal(page.style.navigationBarTitleText, title, `${p} 标题应为「${title}」`);
|
|
// 名片云全部页面都不是 tabBar 根页
|
|
assert.ok(page.style.enablePullDownRefresh === true, `${p} 应开启下拉刷新`);
|
|
}
|
|
const tabPages = (pagesJson.tabBar && pagesJson.tabBar.list ? pagesJson.tabBar.list : []).map(p => p.pagePath);
|
|
for (const p of Object.keys(ADMIN_PAGES)) {
|
|
assert.ok(!tabPages.some(tp => tp.includes(p)), `TabBar 不应含 ${p}`);
|
|
}
|
|
});
|
|
|
|
test('注册: PAGE_KEY_MAP 含商家端 3 页且与 pages.json 一致', () => {
|
|
const map = {
|
|
PAGE_CARD_ADMIN_MANAGE: '/pages_tool/card/admin/manage',
|
|
PAGE_CARD_ADMIN_CONTACTS: '/pages_tool/card/admin/contacts',
|
|
PAGE_CARD_ADMIN_LEADS: '/pages_tool/card/admin/leads'
|
|
};
|
|
for (const [key, val] of Object.entries(map)) {
|
|
const m = utilSrc.match(new RegExp(`${key}:\\s*'([^']+)'`));
|
|
assert.ok(m, `PAGE_KEY_MAP 应有 ${key}`);
|
|
assert.equal(m[1], val);
|
|
assert.ok(toolPaths().includes(val.slice('/pages_tool/'.length)),
|
|
`${val} 未在 pages.json 注册`);
|
|
}
|
|
// 防回归: TAB_BAR_KEYS 不得被误加 admin 路径
|
|
const tabBlock = utilSrc.match(/TAB_BAR_KEYS\s*=\s*\{[\s\S]*?\}/);
|
|
assert.ok(tabBlock, 'TAB_BAR_KEYS 应存在');
|
|
assert.ok(!/admin/.test(tabBlock[0]), 'TAB_BAR_KEYS 不应含 admin 路径');
|
|
});
|
|
|
|
// =============================================================
|
|
// 2. API 收口(与 card-api 测试同一契约, 覆盖新页面)
|
|
// =============================================================
|
|
|
|
test('收口: 商家端 3 页零 /card/api/ 字面量且走 this.$cardApi', () => {
|
|
for (const f of ['manage.vue', 'contacts.vue', 'leads.vue']) {
|
|
const src = readAdmin(f);
|
|
assert.ok(!/['"]\/card\/api\//.test(src), `${f} 仍直连 /card/api/ 字面量`);
|
|
assert.ok(/this\.\$cardApi\./.test(src), `${f} 未使用 this.$cardApi`);
|
|
}
|
|
});
|
|
|
|
test('收口: 商家端页面从 card-api.js 导入共享字典', () => {
|
|
assert.ok(/import\s*\{\s*CONTACT_SOURCES\s*\}\s*from\s*'@\/common\/js\/card-api\.js'/.test(readAdmin('contacts.vue')),
|
|
'contacts.vue 应导入 CONTACT_SOURCES');
|
|
assert.ok(/import\s*\{\s*LEAD_STATUS\s*\}\s*from\s*'@\/common\/js\/card-api\.js'/.test(readAdmin('leads.vue')),
|
|
'leads.vue 应导入 LEAD_STATUS');
|
|
});
|
|
|
|
test('收口: leads.vue 的 setStatus 只接受 0-3(后端约束)', () => {
|
|
const src = readAdmin('leads.vue');
|
|
// ActionSheet 选项来自 LEAD_STATUS(0-3), 不产生越界值
|
|
assert.ok(/itemList:\s*LEAD_STATUS\.map/.test(src), '状态选项必须来自 LEAD_STATUS 字典');
|
|
assert.ok(!/status\s*=\s*[4-9]/.test(src), '不得出现 0-3 之外的 status 值');
|
|
});
|
|
|
|
// =============================================================
|
|
// 3. 鸿蒙安全导航(禁裸 uni.switchTab 静默失败)
|
|
// =============================================================
|
|
|
|
test('鸿蒙: 商家端 3 页平级互链全部走 $util.redirectTo', () => {
|
|
const cross = {
|
|
'manage.vue': [['goContacts', '/pages_tool/card/admin/contacts'], ['goLeads', '/pages_tool/card/admin/leads']],
|
|
'contacts.vue': [['goManage', '/pages_tool/card/admin/manage'], ['goLeads', '/pages_tool/card/admin/leads']],
|
|
'leads.vue': [['goManage', '/pages_tool/card/admin/manage'], ['goContacts', '/pages_tool/card/admin/contacts']]
|
|
};
|
|
for (const [f, pairs] of Object.entries(cross)) {
|
|
const src = readAdmin(f);
|
|
for (const [method, target] of pairs) {
|
|
const m = src.match(new RegExp(`${method}\\(\\)\\s*\\{([\\s\\S]*?)\\n\\t\\t\\}`));
|
|
assert.ok(m, `${f} 应定义 ${method}`);
|
|
assert.ok(/\$util\.redirectTo/.test(m[1]), `${f}#${method} 必须走 $util.redirectTo`);
|
|
assert.ok(new RegExp(`redirectTo\\('${target.replace(/\//g, '\\/')}'\\)`).test(m[1]),
|
|
`${f}#${method} 目标应为 ${target}`);
|
|
}
|
|
assert.ok(!/uni\.switchTab\s*\(/.test(src), `${f} 不得直接调 uni.switchTab`);
|
|
}
|
|
});
|
|
|
|
test('鸿蒙: manage.vue 跳详情/编辑用 navigateTo + encodeURIComponent(card_id)', () => {
|
|
const src = readAdmin('manage.vue');
|
|
// 名片路由参数一律 card_id(字符串), 双编码无害(纯 ASCII)
|
|
assert.ok(/\/pages_tool\/card\/detail\?card_id='\s*\+\s*encodeURIComponent\(item\.card_id\)/.test(src),
|
|
'goDetail 应 encodeURIComponent(card_id)');
|
|
assert.ok(/\/pages_tool\/card\/edit\?card_id='\s*\+\s*encodeURIComponent\(item\.card_id\)/.test(src),
|
|
'goEdit 应 encodeURIComponent(card_id)');
|
|
});
|
|
|
|
// =============================================================
|
|
// 4. 编辑页复用回跳(from=admin)
|
|
// =============================================================
|
|
|
|
test('复用: contact-edit 支持 from=admin 且缺省行为不变', () => {
|
|
const src = readCard('contact-edit.vue');
|
|
assert.ok(/this\.fromAdmin\s*=\s*options\.from\s*===\s*'admin'/.test(src),
|
|
'onLoad 应解析 from=admin');
|
|
const m = src.match(/backToList\(\)\s*\{([\s\S]*?)\n\t\t\}/);
|
|
assert.ok(m, '应能提取 backToList 方法体');
|
|
assert.ok(/\$util\.redirectTo/.test(m[1]), 'backToList 必须走 $util.redirectTo');
|
|
assert.ok(/this\.fromAdmin\s*\?/.test(m[1]), 'backToList 应按来源分支');
|
|
assert.ok(m[1].includes("'/pages_tool/card/admin/contacts'"),
|
|
'商家端应回 /pages_tool/card/admin/contacts');
|
|
assert.ok(m[1].includes("'/pages_tool/card/contacts'"),
|
|
'员工端缺省仍回 /pages_tool/card/contacts(不破坏既有行为)');
|
|
assert.ok(!/uni\.switchTab\s*\(/.test(src), 'contact-edit.vue 不得直接调 uni.switchTab');
|
|
});
|
|
|
|
test('复用: admin/contacts 的编辑入口带 from=admin', () => {
|
|
const src = readAdmin('contacts.vue');
|
|
assert.ok(/contact-edit\?from=admin&id='/.test(src), '编辑入口应带 from=admin&id=');
|
|
assert.ok(/contact-edit\?from=admin'/.test(src), '添加入口应带 from=admin');
|
|
});
|
|
|
|
// =============================================================
|
|
// 5. 条件编译配对平衡(新文件同样纳入)
|
|
// =============================================================
|
|
|
|
test('ifdef 配对平衡: 商家端 3 页', () => {
|
|
for (const f of ['manage.vue', 'contacts.vue', 'leads.vue']) {
|
|
const src = readAdmin(f);
|
|
const open = (src.match(/#ifdef|#ifndef/g) || []).length;
|
|
const close = (src.match(/#endif/g) || []).length;
|
|
assert.equal(open, close, `${f}: #ifdef/#ifndef=${open} 但 #endif=${close}`);
|
|
}
|
|
});
|