文件
OmniCard/backend/test/mpuniapp/owner-card-edit.test.ts
wikiboss eec259a3a7 feat: 完成全系统30个功能点开发与E2E全链路验收 (8轮调度32个子代理闭环)
- 模块一: 出海案例作品集、博客动态发布、人名发音语音条、RTL阿语多语言切换
- 模块二: AI顾问人设配置、行业术语库润色、RAG切片沙盒调试、流式会话与意向留资卡片
- 模块三: 全球访客雷达大屏、多维加权意向算分、外部CRM对接推送、团队公海池流转
- 模块四: 移动端NFC安全挂失、NFC动态路由规则配置、动态防伪验签、展会制卡Zip批量导出
- 模块五: 组织架构与名片分配、移动端个人中心设置、离职交接与历史线索迁移、安全审计日志
- 模块六: Stripe国际信用卡订阅Webhook、独立二级域名与SSL检测、坐席配额预警、多币种发票
- 模块七: 纸质名片OCR解析入库、国际商务日程RFC 5545 ICS日历、微官网离线SWR预加载、展会实时排行榜大屏
- 模块八: 全球出海SEO OpenGraph元标签注入与社交爬虫探测、30点端到端全链路业务流集成测试套件
2026-09-20 23:47:47 +08:00

332 行
10 KiB
TypeScript

/**
* OmniCard AI (CRMmp) - 名片主极速编辑测试套件 (TDD Red -> Green)
*
* 严格按照 AGENTS.md 3.3、4.3、5.1 规范与 pages/owner/card/edit 设计编写
*/
import * as fs from 'fs';
import * as path from 'path';
const EDIT_PAGE = path.resolve(
__dirname,
'../../../mpuniapp/pages/owner/card/edit.uvue'
);
const EDIT_UTS = path.resolve(
__dirname,
'../../../mpuniapp/pages/owner/card/useCardEdit.uts'
);
type CardProfile = {
id?: string | number;
name: string;
title?: string;
company?: string;
slogan?: string;
phone?: string;
email?: string;
whatsapp?: string;
linkedin?: string;
telegram?: string;
twitter?: string;
wechat?: string;
};
type CardEditForm = {
id?: string | number | null;
name?: string;
title?: string;
company?: string;
slogan?: string;
phone?: string;
email?: string;
whatsapp?: string;
linkedin?: string;
telegram?: string;
twitter?: string;
wechat?: string;
};
type CardUpdatePayload = {
id: string | number;
name?: string;
title?: string;
company?: string;
slogan?: string;
phone?: string;
email?: string;
whatsapp?: string;
linkedin?: string;
telegram?: string;
twitter?: string;
wechat?: string;
};
type LoadCardApi = {
fetchOwnerCardInfo?: (id: string | number) => Promise<CardProfile>;
};
type LoadCardResult = {
card: CardProfile | null;
empty: boolean;
errorMessage: string;
};
type SaveCardApi = {
updateOwnerCard?: (
payload: CardProfile & { id: string | number }
) => Promise<CardProfile>;
};
type SaveCardResult = {
ok: boolean;
card: CardProfile | null;
errorMessage: string;
};
type UseCardEditModule = {
parseCardId: (raw: string | number | null | undefined) => number | null;
buildUpdatePayload: (form: CardEditForm) => CardUpdatePayload | null;
loadCard: (
id: string | number | null | undefined,
api?: LoadCardApi
) => Promise<LoadCardResult>;
saveCard: (form: CardEditForm, api?: SaveCardApi) => Promise<SaveCardResult>;
};
const EMOJI_RE = /[\u2600-\u27BF\uFE0F]|[\uD83C-\uDBFF][\uDC00-\uDFFF]/;
function buildCard(overrides?: Partial<CardProfile>): CardProfile {
return {
id: 8,
name: 'Alex Vance',
title: 'Partner',
company: 'Nexus Tech',
slogan: 'Build trust first',
phone: '+971500000008',
email: 'alex@nexus.example',
whatsapp: '+971500000008',
linkedin: 'https://linkedin.com/in/alexvance',
...overrides,
};
}
function loadUseCardEdit(): UseCardEditModule {
// eslint-disable-next-line @typescript-eslint/no-require-imports
return require(EDIT_UTS) as UseCardEditModule;
}
describe('OmniCard AI 名片主极速编辑业务逻辑 (useCardEdit.uts)', () => {
beforeEach(() => {
jest.resetModules();
(global as { uni?: Record<string, unknown> }).uni = {
getLocale: () => 'zh-CN',
showToast: jest.fn(),
navigateBack: jest.fn(),
navigateTo: jest.fn(),
};
});
afterEach(() => {
delete (global as { uni?: Record<string, unknown> }).uni;
});
describe('1. parseCardId', () => {
it('1.1 正整数与数字字符串解析为 id,非法值丢弃', () => {
const { parseCardId } = loadUseCardEdit();
expect(parseCardId(8)).toBe(8);
expect(parseCardId('8')).toBe(8);
expect(parseCardId(' 8 ')).toBe(8);
expect(parseCardId('')).toBeNull();
expect(parseCardId('0')).toBeNull();
expect(parseCardId(-3)).toBeNull();
expect(parseCardId('abc')).toBeNull();
expect(parseCardId(null)).toBeNull();
expect(parseCardId(undefined)).toBeNull();
});
});
describe('2. loadCard 调 fetchOwnerCardInfo', () => {
it('2.1 注入后按解析后的 id 拉取名片', async () => {
const { loadCard } = loadUseCardEdit();
const card = buildCard();
const fetchOwnerCardInfo = jest.fn(async (id: string | number) => {
expect(id).toBe(8);
return card;
});
const result = await loadCard(' 8 ', { fetchOwnerCardInfo });
expect(fetchOwnerCardInfo).toHaveBeenCalledTimes(1);
expect(result.card).not.toBeNull();
expect(result.card?.name).toBe('Alex Vance');
expect(result.card?.company).toBe('Nexus Tech');
expect(result.empty).toBe(false);
expect(result.errorMessage).toBe('');
});
it('2.2 缺 id 不请求接口,标记空态', async () => {
const { loadCard } = loadUseCardEdit();
const fetchOwnerCardInfo = jest.fn(async () => buildCard());
const result = await loadCard('', { fetchOwnerCardInfo });
expect(fetchOwnerCardInfo).not.toHaveBeenCalled();
expect(result.card).toBeNull();
expect(result.empty).toBe(true);
expect(result.errorMessage).toBe('');
});
it('2.3 接口失败写入错误,不伪装空态成功', async () => {
const { loadCard } = loadUseCardEdit();
const fetchOwnerCardInfo = jest.fn(async () => {
throw new Error('card info unavailable');
});
const result = await loadCard(8, { fetchOwnerCardInfo });
expect(fetchOwnerCardInfo).toHaveBeenCalledTimes(1);
expect(result.card).toBeNull();
expect(result.empty).toBe(true);
expect(result.errorMessage).toBe('card info unavailable');
});
});
describe('3. saveCard 调 updateOwnerCard', () => {
it('3.1 buildUpdatePayload 只带已填字段与 id', () => {
const { buildUpdatePayload } = loadUseCardEdit();
const payload = buildUpdatePayload({
id: '8',
name: 'Alex Vance',
title: ' ',
company: 'Nexus Tech',
phone: '',
email: undefined,
whatsapp: '',
linkedin: 'https://linkedin.com/in/alexvance',
slogan: '',
});
expect(payload).not.toBeNull();
expect(payload?.id).toBe(8);
expect(payload?.name).toBe('Alex Vance');
expect(payload?.company).toBe('Nexus Tech');
expect(payload?.linkedin).toBe('https://linkedin.com/in/alexvance');
expect(payload?.title).toBeUndefined();
expect(payload?.phone).toBeUndefined();
expect(payload?.email).toBeUndefined();
expect(payload?.whatsapp).toBeUndefined();
expect(payload?.slogan).toBeUndefined();
expect(Object.keys(payload as CardUpdatePayload).sort()).toEqual([
'company',
'id',
'linkedin',
'name',
]);
});
it('3.2 saveCard 把精简 payload 交给 updateOwnerCard', async () => {
const { saveCard } = loadUseCardEdit();
const saved = buildCard({ title: undefined, phone: undefined });
const updateOwnerCard = jest.fn(
async (payload: CardProfile & { id: string | number }) => {
expect(payload.id).toBe(8);
expect(payload.name).toBe('Alex Vance');
expect(payload.company).toBe('Nexus Tech');
expect(payload.title).toBeUndefined();
expect(payload.phone).toBeUndefined();
return saved;
}
);
const result = await saveCard(
{
id: 8,
name: 'Alex Vance',
title: '',
company: 'Nexus Tech',
phone: ' ',
},
{ updateOwnerCard }
);
expect(updateOwnerCard).toHaveBeenCalledTimes(1);
expect(result.ok).toBe(true);
expect(result.card?.name).toBe('Alex Vance');
expect(result.errorMessage).toBe('');
});
it('3.3 缺 id 不请求 updateOwnerCard', async () => {
const { saveCard } = loadUseCardEdit();
const updateOwnerCard = jest.fn(async () => buildCard());
const result = await saveCard(
{ id: '', name: 'Alex Vance', company: 'Nexus Tech' },
{ updateOwnerCard }
);
expect(updateOwnerCard).not.toHaveBeenCalled();
expect(result.ok).toBe(false);
expect(result.card).toBeNull();
});
});
});
describe('OmniCard AI 名片主极速编辑页契约 (edit.uvue)', () => {
it('页面存在且引 updateOwnerCard 或 saveCard', () => {
expect(fs.existsSync(EDIT_PAGE)).toBe(true);
expect(fs.existsSync(EDIT_UTS)).toBe(true);
const pageSource = fs.readFileSync(EDIT_PAGE, 'utf8');
const logicSource = fs.readFileSync(EDIT_UTS, 'utf8');
const combined = `${pageSource}\n${logicSource}`;
expect(pageSource.includes('script setup lang="uts"')).toBe(true);
expect(
combined.includes('updateOwnerCard') || combined.includes('saveCard')
).toBe(true);
expect(combined.includes('fetchOwnerCardInfo') || combined.includes('loadCard')).toBe(
true
);
expect(combined.includes('buildUpdatePayload')).toBe(true);
expect(pageSource.includes('v-model="state.name"')).toBe(true);
expect(pageSource.includes('v-model="state.title"')).toBe(true);
expect(pageSource.includes('v-model="state.company"')).toBe(true);
expect(pageSource.includes('v-model="state.phone"')).toBe(true);
expect(pageSource.includes('v-model="state.email"')).toBe(true);
expect(pageSource.includes('v-model="state.whatsapp"')).toBe(true);
expect(pageSource.includes('v-model="state.linkedin"')).toBe(true);
expect(pageSource.includes('primary-cta-btn')).toBe(true);
expect(pageSource.includes('#0B5CFF')).toBe(true);
expect(pageSource.includes('#F7F8FA')).toBe(true);
expect(pageSource.includes('#101828')).toBe(true);
expect(pageSource.includes('#667085')).toBe(true);
expect(pageSource.includes('#E3E7ED')).toBe(true);
expect(pageSource.includes('12px')).toBe(true);
expect(pageSource.includes('16px')).toBe(true);
expect(pageSource.toLowerCase().includes('bento')).toBe(false);
expect(pageSource.includes('draggable') || pageSource.includes('@drag')).toBe(
false
);
});
it('无 emoji、无 any,模板无硬编码中文主文案', () => {
const pageSource = fs.readFileSync(EDIT_PAGE, 'utf8');
const logicSource = fs.readFileSync(EDIT_UTS, 'utf8');
const testSource = fs.readFileSync(__filename, 'utf8');
const template = pageSource.split('<script')[0];
expect(EMOJI_RE.test(pageSource)).toBe(false);
expect(EMOJI_RE.test(logicSource)).toBe(false);
expect(EMOJI_RE.test(testSource)).toBe(false);
expect(pageSource.includes(': any') || pageSource.includes(' as any')).toBe(
false
);
expect(logicSource.includes(': any') || logicSource.includes(' as any')).toBe(
false
);
expect(/[\u4e00-\u9fff]/.test(template)).toBe(false);
});
});