文件
OmniCard/backend/test/mpuniapp/owner-card-edit.test.ts
wikiboss 62e0ae4435 feat: 同步管理端、用户端与后端业务,并接通线上 Web 接口
补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-23 18:01:47 +08:00

854 行
27 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 BrandKitFixture = {
logoUrl?: string;
primaryColor?: string;
lockedFields?: string[] | null;
whitelist?: string[] | null;
};
type CardProfile = {
id?: string | number;
name: string;
title?: string;
company?: string;
slogan?: string;
avatarUrl?: string;
phone?: string;
email?: string;
whatsapp?: string;
linkedin?: string;
telegram?: string;
twitter?: string;
wechat?: string;
timezone?: string;
lockedFields?: string[] | null;
brandKit?: BrandKitFixture | null;
};
type CardEditForm = {
id?: string | number | null;
name?: string;
title?: string;
company?: string;
slogan?: string;
avatarUrl?: string;
phone?: string;
email?: string;
whatsapp?: string;
linkedin?: string;
telegram?: string;
twitter?: string;
wechat?: string;
timezone?: string;
lockedFields?: string[] | null;
};
type CardUpdatePayload = {
id: string | number;
name?: string;
title?: string;
company?: string;
slogan?: string;
avatarUrl?: string;
phone?: string;
email?: string;
whatsapp?: string;
linkedin?: string;
telegram?: string;
twitter?: string;
wechat?: string;
timezone?: 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;
code?: number;
card: CardProfile | null;
errorMessage: string;
};
type CardEditState = {
rawId: string;
cardId: number | null;
name: string;
title: string;
company: string;
slogan: string;
avatarUrl: string;
phone: string;
email: string;
whatsapp: string;
linkedin: string;
timezone: string;
moduleOrder: string[];
lockedFields: string[];
loading: boolean;
saving: boolean;
empty: boolean;
missingId: boolean;
errorMessage: string;
};
type UseCardEditModule = {
parseCardId: (raw: string | number | null | undefined) => number | null;
buildUpdatePayload: (
form: CardEditForm,
lockedFields?: string[] | null
) => CardUpdatePayload | null;
extractLockedFields: (
source: CardProfile | BrandKitFixture | null | undefined
) => string[];
isFieldLocked: (
lockedFields: string[] | null | undefined,
field: string
) => boolean;
resolveCardEditText: (key: string) => string;
createCardEditState: () => CardEditState;
applyLoadResult: (state: CardEditState, result: LoadCardResult) => void;
formFromState: (state: CardEditState) => CardEditForm;
DEFAULT_MODULE_ORDER: string[];
normalizeModuleOrder: (raw: unknown) => string[];
moveModuleInOrder: (
order: string[],
index: number,
delta: number
) => string[];
loadCard: (
id: string | number | null | undefined,
api?: LoadCardApi
) => Promise<LoadCardResult>;
saveCard: (
form: CardEditForm,
api?: SaveCardApi,
lockedFields?: string[] | null
) => 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('4. 品牌套件 lockedFields 锁定', () => {
it('4.1 无 lockedFields / 空数组 / 仅有 brandKit 资产时不锁任何字段', () => {
const { extractLockedFields, isFieldLocked, buildUpdatePayload } =
loadUseCardEdit();
expect(extractLockedFields(null)).toEqual([]);
expect(extractLockedFields(undefined)).toEqual([]);
expect(extractLockedFields(buildCard())).toEqual([]);
expect(
extractLockedFields(
buildCard({
brandKit: { logoUrl: 'https://cdn.example/logo.png', primaryColor: '#0088FF' },
})
)
).toEqual([]);
expect(
extractLockedFields(buildCard({ lockedFields: [] }))
).toEqual([]);
expect(
extractLockedFields(buildCard({ brandKit: { lockedFields: null } }))
).toEqual([]);
expect(isFieldLocked([], 'company')).toBe(false);
expect(isFieldLocked(null, 'company')).toBe(false);
expect(isFieldLocked(undefined, 'name')).toBe(false);
const payload = buildUpdatePayload(
{
id: 8,
name: 'Alex Vance',
company: 'Edited Co',
avatarUrl: 'https://cdn.example/a.png',
},
[]
);
expect(payload?.company).toBe('Edited Co');
expect(payload?.name).toBe('Alex Vance');
expect(payload?.avatarUrl).toBe('https://cdn.example/a.png');
});
it('4.2 读取 brandKit.lockedFields 与顶层 lockedFields,别名映射生效', () => {
const { extractLockedFields, isFieldLocked } = loadUseCardEdit();
expect(
extractLockedFields(
buildCard({
brandKit: {
logoUrl: 'https://cdn.example/logo.png',
primaryColor: '#0088FF',
lockedFields: ['companyName', 'logoUrl', 'primaryColor'],
},
})
)
).toEqual(['companyName', 'logoUrl', 'primaryColor']);
expect(
extractLockedFields(
buildCard({ lockedFields: ['company', 'logo'] })
)
).toEqual(['company', 'logo']);
const locked = ['companyName', 'logoUrl', 'primaryColor'];
expect(isFieldLocked(locked, 'company')).toBe(true);
expect(isFieldLocked(locked, 'companyName')).toBe(true);
expect(isFieldLocked(locked, 'logoUrl')).toBe(true);
expect(isFieldLocked(locked, 'logo')).toBe(true);
expect(isFieldLocked(locked, 'primaryColor')).toBe(true);
expect(isFieldLocked(locked, 'name')).toBe(false);
expect(isFieldLocked(locked, 'phone')).toBe(false);
});
it('4.3 whitelist 存在时,未列入白名单的品牌字段视为锁定', () => {
const { extractLockedFields, isFieldLocked } = loadUseCardEdit();
const locked = extractLockedFields(
buildCard({
brandKit: {
logoUrl: 'https://cdn.example/logo.png',
primaryColor: '#0088FF',
whitelist: ['name', 'phone', 'email', 'avatarUrl'],
},
})
);
expect(locked).toContain('company');
expect(locked).toContain('logoUrl');
expect(locked).toContain('primaryColor');
expect(isFieldLocked(locked, 'company')).toBe(true);
expect(isFieldLocked(locked, 'name')).toBe(false);
expect(isFieldLocked(locked, 'phone')).toBe(false);
});
it('4.4 buildUpdatePayload / saveCard 不提交锁定字段的新值', async () => {
const { buildUpdatePayload, saveCard } = loadUseCardEdit();
const locked = ['companyName', 'logoUrl', 'primaryColor'];
const payload = buildUpdatePayload(
{
id: 8,
name: 'Alex Vance',
company: 'Hacked Corp',
avatarUrl: 'https://cdn.example/me.png',
phone: '+971500000008',
},
locked
);
expect(payload).not.toBeNull();
expect(payload?.name).toBe('Alex Vance');
expect(payload?.phone).toBe('+971500000008');
// 品牌 Logo 锁定不影响个人头像提交
expect(payload?.avatarUrl).toBe('https://cdn.example/me.png');
expect(payload?.company).toBeUndefined();
const updateOwnerCard = jest.fn(
async (body: CardProfile & { id: string | number }) => {
expect(body.company).toBeUndefined();
expect(body.avatarUrl).toBe('https://cdn.example/me.png');
expect(body.name).toBe('Alex Vance');
return buildCard({ name: body.name });
}
);
const result = await saveCard(
{
id: 8,
name: 'Alex Vance',
company: 'Hacked Corp',
avatarUrl: 'https://cdn.example/me.png',
},
{ updateOwnerCard },
locked
);
expect(updateOwnerCard).toHaveBeenCalledTimes(1);
expect(result.ok).toBe(true);
});
it('4.5 applyLoadResult 写入 lockedFields;resolve 提供锁定提示文案', () => {
const {
createCardEditState,
applyLoadResult,
formFromState,
resolveCardEditText,
isFieldLocked,
} = loadUseCardEdit();
const state = createCardEditState();
applyLoadResult(state, {
card: buildCard({
brandKit: {
lockedFields: ['companyName', 'logoUrl'],
},
}),
empty: false,
errorMessage: '',
});
expect(state.lockedFields).toEqual(['companyName', 'logoUrl']);
expect(isFieldLocked(state.lockedFields, 'company')).toBe(true);
expect(formFromState(state).lockedFields).toEqual([
'companyName',
'logoUrl',
]);
expect(resolveCardEditText('lockedHint')).toBe('品牌规范已锁定,不可修改');
});
it('4.6 resolveCardEditText(lockedHint) 英文自然文案', () => {
(global as { uni?: Record<string, unknown> }).uni = {
getLocale: () => 'en-US',
showToast: jest.fn(),
navigateBack: jest.fn(),
navigateTo: jest.fn(),
};
jest.resetModules();
const { resolveCardEditText } = loadUseCardEdit();
expect(resolveCardEditText('lockedHint')).toBe(
'Locked by brand kit'
);
});
});
describe('5. slogan 口号字段', () => {
it('5.1 applyLoadResult / formFromState 读写 slogan,空口号不进 payload', () => {
const {
createCardEditState,
applyLoadResult,
formFromState,
buildUpdatePayload,
resolveCardEditText,
} = loadUseCardEdit();
const state = createCardEditState();
expect(state.slogan).toBe('');
applyLoadResult(state, {
card: buildCard({ slogan: 'Build trust first' }),
empty: false,
errorMessage: '',
});
expect(state.slogan).toBe('Build trust first');
expect(formFromState(state).slogan).toBe('Build trust first');
const filled = buildUpdatePayload({
id: 8,
name: 'Alex Vance',
slogan: ' Build trust first ',
});
expect(filled?.slogan).toBe('Build trust first');
const emptySlogan = buildUpdatePayload({
id: 8,
name: 'Alex Vance',
slogan: ' ',
});
expect(emptySlogan?.slogan).toBeUndefined();
expect(Object.keys(emptySlogan as CardUpdatePayload)).not.toContain(
'slogan'
);
expect(resolveCardEditText('slogan')).toBe('口号');
});
it('5.2 saveCard 提交已填 slogan,不写入假文案', async () => {
const { saveCard } = loadUseCardEdit();
const updateOwnerCard = jest.fn(
async (payload: CardProfile & { id: string | number }) => {
expect(payload.slogan).toBe('Trusted globally');
return buildCard({ slogan: payload.slogan });
}
);
const ok = await saveCard(
{
id: 8,
name: 'Alex Vance',
slogan: 'Trusted globally',
},
{ updateOwnerCard }
);
expect(ok.ok).toBe(true);
expect(updateOwnerCard).toHaveBeenCalledTimes(1);
const updateEmpty = jest.fn(
async (payload: CardProfile & { id: string | number }) => {
expect(payload.slogan).toBeUndefined();
return buildCard();
}
);
await saveCard(
{ id: 8, name: 'Alex Vance', slogan: '' },
{ updateOwnerCard: updateEmpty }
);
expect(updateEmpty).toHaveBeenCalledTimes(1);
});
it('5.3 锁定 slogan 时不提交', () => {
const { buildUpdatePayload } = loadUseCardEdit();
const payload = buildUpdatePayload(
{
id: 8,
name: 'Alex Vance',
slogan: 'Should not send',
},
['slogan']
);
expect(payload?.name).toBe('Alex Vance');
expect(payload?.slogan).toBeUndefined();
});
});
describe('6. timezone 时区字段', () => {
it('6.1 applyLoadResult / formFromState 读写 timezone,空时区不进 payload、无默认值', () => {
const {
createCardEditState,
applyLoadResult,
formFromState,
buildUpdatePayload,
resolveCardEditText,
} = loadUseCardEdit();
const state = createCardEditState();
expect(state.timezone).toBe('');
expect(state.timezone).not.toBe('UTC+8');
expect(state.timezone).not.toBe('Asia/Shanghai');
applyLoadResult(state, {
card: buildCard({ timezone: 'Asia/Dubai' }),
empty: false,
errorMessage: '',
});
expect(state.timezone).toBe('Asia/Dubai');
expect(formFromState(state).timezone).toBe('Asia/Dubai');
const filled = buildUpdatePayload({
id: 8,
name: 'Alex Vance',
timezone: ' America/New_York ',
});
expect(filled?.timezone).toBe('America/New_York');
const emptyTz = buildUpdatePayload({
id: 8,
name: 'Alex Vance',
timezone: ' ',
});
expect(emptyTz?.timezone).toBeUndefined();
expect(Object.keys(emptyTz as CardUpdatePayload)).not.toContain('timezone');
expect(Object.prototype.hasOwnProperty.call(emptyTz, 'timezone')).toBe(
false
);
const omitted = buildUpdatePayload({
id: 8,
name: 'Alex Vance',
timezone: '',
});
expect(omitted?.timezone).toBeUndefined();
expect(Object.keys(omitted as CardUpdatePayload)).not.toContain(
'timezone'
);
expect(resolveCardEditText('timezone')).toBe('时区');
});
it('6.2 saveCard 提交已填 timezone;清空时不带 timezone、不默认 UTC+8/Asia/Shanghai', async () => {
const { saveCard } = loadUseCardEdit();
const updateOwnerCard = jest.fn(
async (payload: CardProfile & { id: string | number }) => {
expect(payload.timezone).toBe('Europe/Paris');
return buildCard({ timezone: payload.timezone });
}
);
const ok = await saveCard(
{
id: 8,
name: 'Alex Vance',
timezone: 'Europe/Paris',
},
{ updateOwnerCard }
);
expect(ok.ok).toBe(true);
expect(ok.code).toBe(1000);
expect(updateOwnerCard).toHaveBeenCalledTimes(1);
const updateEmpty = jest.fn(
async (payload: CardProfile & { id: string | number }) => {
expect(payload.timezone).toBeUndefined();
expect(
Object.prototype.hasOwnProperty.call(payload, 'timezone')
).toBe(false);
expect(payload.timezone).not.toBe('UTC+8');
expect(payload.timezone).not.toBe('Asia/Shanghai');
return buildCard({ timezone: undefined });
}
);
const cleared = await saveCard(
{ id: 8, name: 'Alex Vance', timezone: '' },
{ updateOwnerCard: updateEmpty }
);
expect(cleared.ok).toBe(true);
expect(cleared.code).toBe(1000);
expect(updateEmpty).toHaveBeenCalledTimes(1);
const sent = updateEmpty.mock.calls[0][0] as Record<string, unknown>;
expect(Object.keys(sent)).not.toContain('timezone');
});
it('6.3 saveCard 失败时 code 不为 1000、ok 为 false', async () => {
const { saveCard } = loadUseCardEdit();
const updateOwnerCard = jest.fn(async () => {
throw new Error('update failed');
});
const result = await saveCard(
{ id: 8, name: 'Alex Vance', timezone: 'UTC+4' },
{ updateOwnerCard }
);
expect(result.ok).toBe(false);
expect(result.code).not.toBe(1000);
expect(result.card).toBeNull();
});
});
describe('7. 模块排序(无服务端排序字段,走客户端顺序)', () => {
it('7.1 默认顺序与 normalize / move 可编辑且不伪造 API 字段', () => {
const {
DEFAULT_MODULE_ORDER,
normalizeModuleOrder,
moveModuleInOrder,
createCardEditState,
buildUpdatePayload,
formFromState,
} = loadUseCardEdit();
expect(DEFAULT_MODULE_ORDER).toEqual(['basic', 'social', 'privacy']);
expect(normalizeModuleOrder(null)).toEqual(DEFAULT_MODULE_ORDER);
expect(normalizeModuleOrder(['privacy', 'basic', 'extra'])).toEqual([
'privacy',
'basic',
'social',
]);
const moved = moveModuleInOrder(
['basic', 'social', 'privacy'],
0,
1
);
expect(moved).toEqual(['social', 'basic', 'privacy']);
expect(
moveModuleInOrder(['basic', 'social', 'privacy'], 0, -1)
).toEqual(['basic', 'social', 'privacy']);
const state = createCardEditState();
expect(state.moduleOrder).toEqual(DEFAULT_MODULE_ORDER);
state.moduleOrder = moved;
const form = formFromState(state);
expect((form as { moduleOrder?: string[] }).moduleOrder).toBeUndefined();
const payload = buildUpdatePayload({
id: 8,
name: 'Alex Vance',
slogan: 'Hi',
});
expect(
Object.prototype.hasOwnProperty.call(payload, 'moduleOrder')
).toBe(false);
});
});
});
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(logicSource.includes('export {') && logicSource.includes('fetchOwnerCardInfo')).toBe(true);
expect(combined.includes('buildUpdatePayload')).toBe(true);
expect(combined.includes('extractLockedFields')).toBe(true);
expect(combined.includes('isFieldLocked')).toBe(true);
expect(combined.includes('lockedFields')).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.slogan"')).toBe(true);
expect(pageSource.includes('v-model="state.timezone"')).toBe(true);
expect(
pageSource.includes('isFieldLocked(state.lockedFields, \'company\')') ||
pageSource.includes('isFieldLocked(state.lockedFields, "company")') ||
pageSource.includes('companyLocked')
).toBe(true);
expect(pageSource.includes('lockedHint') || pageSource.includes("t('lockedHint')") || pageSource.includes('resolveCardEditText(\'lockedHint\')')).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('code === 1000') ||
logicSource.includes('code === 1000')
).toBe(true);
expect(logicSource.includes('UTC+8') || pageSource.includes('UTC+8')).toBe(
false
);
expect(
logicSource.includes('Asia/Shanghai') ||
pageSource.includes('Asia/Shanghai')
).toBe(false);
expect(
pageSource.includes('state.moduleOrder') ||
pageSource.includes('handleMoveModule') ||
logicSource.includes('moveModuleInOrder')
).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);
});
});