补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。 Co-authored-by: Cursor <cursoragent@cursor.com>
620 行
22 KiB
TypeScript
620 行
22 KiB
TypeScript
/**
|
||
* OmniCard AI (CRMmp) - 名片主个人中心与消息通知配置测试套件 (TDD Red -> Green)
|
||
*
|
||
* 严格按照 AGENTS.md 3.3、4.3、5.1 规范与 pages/owner/profile 设计编写
|
||
*/
|
||
|
||
import * as fs from 'fs';
|
||
import * as path from 'path';
|
||
|
||
const PROFILE_PAGE = path.resolve(
|
||
__dirname,
|
||
'../../../mpuniapp/pages/owner/profile/index.uvue'
|
||
);
|
||
const PROFILE_UTS = path.resolve(
|
||
__dirname,
|
||
'../../../mpuniapp/pages/owner/profile/useProfile.uts'
|
||
);
|
||
const PAGES_JSON = path.resolve(
|
||
__dirname,
|
||
'../../../mpuniapp/pages.json'
|
||
);
|
||
|
||
type OmniLocale =
|
||
| 'zh-CN'
|
||
| 'en-US'
|
||
| 'ar-SA'
|
||
| 'es-ES'
|
||
| 'de-DE'
|
||
| 'fr-FR'
|
||
| 'ja-JP';
|
||
|
||
type UserProfileAccount = {
|
||
name: string;
|
||
company: string;
|
||
department: string;
|
||
avatar: string;
|
||
};
|
||
|
||
type NotificationSettings = {
|
||
radarPush: boolean;
|
||
leadNotice: boolean;
|
||
};
|
||
|
||
type ProfileState = {
|
||
account: UserProfileAccount;
|
||
notifications: NotificationSettings;
|
||
locale: OmniLocale;
|
||
saving: boolean;
|
||
loading: boolean;
|
||
errorMessage: string;
|
||
};
|
||
|
||
type StorageApi = {
|
||
getStorageSync: (key: string) => unknown;
|
||
setStorageSync: (key: string, value: unknown) => void;
|
||
};
|
||
|
||
type CardProfileFixture = {
|
||
id?: string | number;
|
||
name: string;
|
||
title?: string;
|
||
company?: string;
|
||
department?: string;
|
||
avatarUrl?: string;
|
||
};
|
||
|
||
type OwnerPageQuery = {
|
||
page: number;
|
||
size: number;
|
||
keyWord?: string;
|
||
};
|
||
|
||
type OwnerPageData<T> = {
|
||
list: T[];
|
||
pagination: {
|
||
page: number;
|
||
size: number;
|
||
total: number;
|
||
};
|
||
};
|
||
|
||
type LoadProfileAccountApi = {
|
||
fetchOwnerCardPage: (
|
||
query: OwnerPageQuery
|
||
) => Promise<OwnerPageData<CardProfileFixture>>;
|
||
};
|
||
|
||
type UseProfileModule = {
|
||
PROFILE_ACCOUNT_STORAGE_KEY: string;
|
||
PROFILE_NOTIFICATIONS_STORAGE_KEY: string;
|
||
PORTAL_LOCALE_STORAGE_KEY: string;
|
||
getDefaultAccount: () => UserProfileAccount;
|
||
getDefaultNotifications: () => NotificationSettings;
|
||
createProfileState: (locale?: OmniLocale) => ProfileState;
|
||
loadProfileSettings: (storage?: StorageApi) => ProfileState;
|
||
saveProfileSettings: (state: ProfileState, storage?: StorageApi) => boolean;
|
||
updateAccount: (state: ProfileState, patch: Partial<UserProfileAccount>) => void;
|
||
toggleRadarPush: (state: ProfileState, value?: boolean, storage?: StorageApi) => boolean;
|
||
toggleLeadNotice: (state: ProfileState, value?: boolean, storage?: StorageApi) => boolean;
|
||
setPreferredLocale: (state: ProfileState, locale: string, storage?: StorageApi) => OmniLocale;
|
||
calculateQuotaPercent: (used: number, limit: number) => number;
|
||
resolveProfileText: (key: string) => string;
|
||
displayOrgField: (value: string | null | undefined) => string;
|
||
clearOrgFields: (state: ProfileState) => void;
|
||
loadOwnerProfileAccount: (
|
||
state: ProfileState,
|
||
api?: LoadProfileAccountApi
|
||
) => Promise<boolean>;
|
||
};
|
||
|
||
const EMOJI_RE = /[\u2600-\u27BF\uFE0F]|[\uD83C-\uDBFF][\uDC00-\uDFFF]/;
|
||
const SAMPLE_ORG_RE =
|
||
/Nexus Tech Global|Overseas Business Dept|Cyberdyne Systems|Global Trade Co\.|示例(公司|组织|部门)/;
|
||
|
||
function loadUseProfile(): UseProfileModule {
|
||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||
return require(PROFILE_UTS) as UseProfileModule;
|
||
}
|
||
|
||
describe('OmniCard AI 名片主个人中心与消息通知配置 (useProfile.uts)', () => {
|
||
let mockStorage: Record<string, unknown> = {};
|
||
|
||
beforeEach(() => {
|
||
jest.resetModules();
|
||
mockStorage = {};
|
||
(global as { uni?: Record<string, unknown> }).uni = {
|
||
getLocale: () => 'zh-CN',
|
||
showToast: jest.fn(),
|
||
navigateTo: jest.fn(),
|
||
navigateBack: jest.fn(),
|
||
getStorageSync: jest.fn((key: string) => mockStorage[key] ?? ''),
|
||
setStorageSync: jest.fn((key: string, val: unknown) => {
|
||
mockStorage[key] = val;
|
||
}),
|
||
};
|
||
});
|
||
|
||
afterEach(() => {
|
||
delete (global as { uni?: Record<string, unknown> }).uni;
|
||
});
|
||
|
||
describe('1. 配置读取与初始化 (loadProfileSettings)', () => {
|
||
it('1.1 首次无缓存时,公司/部门为空串,通知开关开启', () => {
|
||
const { loadProfileSettings, getDefaultAccount } = loadUseProfile();
|
||
const defaults = getDefaultAccount();
|
||
const state = loadProfileSettings();
|
||
|
||
expect(defaults.company).toBe('');
|
||
expect(defaults.department).toBe('');
|
||
expect(SAMPLE_ORG_RE.test(defaults.company)).toBe(false);
|
||
expect(SAMPLE_ORG_RE.test(defaults.department)).toBe(false);
|
||
expect(state.account.company).toBe('');
|
||
expect(state.account.department).toBe('');
|
||
expect(state.notifications.radarPush).toBe(true);
|
||
expect(state.notifications.leadNotice).toBe(true);
|
||
expect(state.locale).toBe('zh-CN');
|
||
expect(state.errorMessage).toBe('');
|
||
});
|
||
|
||
it('1.2 本地有缓存时,正确恢复已保存的账户与通知开关', () => {
|
||
const {
|
||
loadProfileSettings,
|
||
PROFILE_ACCOUNT_STORAGE_KEY,
|
||
PROFILE_NOTIFICATIONS_STORAGE_KEY,
|
||
} = loadUseProfile();
|
||
|
||
mockStorage[PROFILE_ACCOUNT_STORAGE_KEY] = JSON.stringify({
|
||
name: 'Sarah Connor',
|
||
company: 'Acme Corp',
|
||
department: 'Security Division',
|
||
avatar: 'https://example.com/sarah.jpg',
|
||
});
|
||
mockStorage[PROFILE_NOTIFICATIONS_STORAGE_KEY] = JSON.stringify({
|
||
radarPush: false,
|
||
leadNotice: true,
|
||
});
|
||
|
||
const state = loadProfileSettings();
|
||
expect(state.account.name).toBe('Sarah Connor');
|
||
expect(state.account.company).toBe('Acme Corp');
|
||
expect(state.account.department).toBe('Security Division');
|
||
expect(state.account.avatar).toBe('https://example.com/sarah.jpg');
|
||
expect(state.notifications.radarPush).toBe(false);
|
||
expect(state.notifications.leadNotice).toBe(true);
|
||
});
|
||
|
||
it('1.3 防御保护:缓存数据损坏时不抛错,公司/部门保持空串', () => {
|
||
const {
|
||
loadProfileSettings,
|
||
PROFILE_ACCOUNT_STORAGE_KEY,
|
||
PROFILE_NOTIFICATIONS_STORAGE_KEY,
|
||
} = loadUseProfile();
|
||
|
||
// 非法 JSON 字符串
|
||
mockStorage[PROFILE_ACCOUNT_STORAGE_KEY] = '{invalid_json--';
|
||
// 错误的通知配置类型
|
||
mockStorage[PROFILE_NOTIFICATIONS_STORAGE_KEY] = 'not-an-object';
|
||
|
||
const state = loadProfileSettings();
|
||
expect(state.account.company).toBe('');
|
||
expect(state.account.department).toBe('');
|
||
expect(state.notifications.radarPush).toBe(true);
|
||
expect(state.notifications.leadNotice).toBe(true);
|
||
expect(state.errorMessage).toBe('');
|
||
});
|
||
|
||
it('1.4 防御保护:缓存缺公司/部门时保持空串,不回填示例组织', () => {
|
||
const {
|
||
loadProfileSettings,
|
||
PROFILE_ACCOUNT_STORAGE_KEY,
|
||
PROFILE_NOTIFICATIONS_STORAGE_KEY,
|
||
} = loadUseProfile();
|
||
|
||
mockStorage[PROFILE_ACCOUNT_STORAGE_KEY] = JSON.stringify({
|
||
name: 'John Doe',
|
||
});
|
||
mockStorage[PROFILE_NOTIFICATIONS_STORAGE_KEY] = JSON.stringify({
|
||
radarPush: false,
|
||
});
|
||
|
||
const state = loadProfileSettings();
|
||
expect(state.account.name).toBe('John Doe');
|
||
expect(state.account.company).toBe('');
|
||
expect(state.account.department).toBe('');
|
||
expect(state.notifications.radarPush).toBe(false);
|
||
expect(state.notifications.leadNotice).toBe(true);
|
||
});
|
||
});
|
||
|
||
describe('2. 通知开关切换与持久化同步 (toggleRadarPush & toggleLeadNotice)', () => {
|
||
it('2.1 toggleRadarPush 支持反转与显式赋值,并同步写入存储', () => {
|
||
const {
|
||
loadProfileSettings,
|
||
toggleRadarPush,
|
||
PROFILE_NOTIFICATIONS_STORAGE_KEY,
|
||
} = loadUseProfile();
|
||
const state = loadProfileSettings();
|
||
expect(state.notifications.radarPush).toBe(true);
|
||
|
||
// 反转切换
|
||
const res1 = toggleRadarPush(state);
|
||
expect(res1).toBe(false);
|
||
expect(state.notifications.radarPush).toBe(false);
|
||
|
||
// 验证本地存储已同步
|
||
const rawStored1 = mockStorage[PROFILE_NOTIFICATIONS_STORAGE_KEY] as string;
|
||
const parsed1 = JSON.parse(rawStored1) as NotificationSettings;
|
||
expect(parsed1.radarPush).toBe(false);
|
||
|
||
// 显式赋值为 true
|
||
const res2 = toggleRadarPush(state, true);
|
||
expect(res2).toBe(true);
|
||
expect(state.notifications.radarPush).toBe(true);
|
||
|
||
const rawStored2 = mockStorage[PROFILE_NOTIFICATIONS_STORAGE_KEY] as string;
|
||
const parsed2 = JSON.parse(rawStored2) as NotificationSettings;
|
||
expect(parsed2.radarPush).toBe(true);
|
||
});
|
||
|
||
it('2.2 toggleLeadNotice 支持反转与显式赋值,并同步写入存储', () => {
|
||
const {
|
||
loadProfileSettings,
|
||
toggleLeadNotice,
|
||
PROFILE_NOTIFICATIONS_STORAGE_KEY,
|
||
} = loadUseProfile();
|
||
const state = loadProfileSettings();
|
||
expect(state.notifications.leadNotice).toBe(true);
|
||
|
||
const res1 = toggleLeadNotice(state, false);
|
||
expect(res1).toBe(false);
|
||
expect(state.notifications.leadNotice).toBe(false);
|
||
|
||
const parsed1 = JSON.parse(
|
||
mockStorage[PROFILE_NOTIFICATIONS_STORAGE_KEY] as string
|
||
) as NotificationSettings;
|
||
expect(parsed1.leadNotice).toBe(false);
|
||
|
||
const res2 = toggleLeadNotice(state);
|
||
expect(res2).toBe(true);
|
||
expect(state.notifications.leadNotice).toBe(true);
|
||
});
|
||
|
||
it('2.3 防御保护:存储写入异常时不崩溃,安全更新 state 并记录错误', () => {
|
||
const { loadProfileSettings, toggleRadarPush } = loadUseProfile();
|
||
const state = loadProfileSettings();
|
||
|
||
const faultyStorage: StorageApi = {
|
||
getStorageSync: jest.fn(),
|
||
setStorageSync: jest.fn(() => {
|
||
throw new Error('QuotaExceededError: LocalStorage limit reached');
|
||
}),
|
||
};
|
||
|
||
const result = toggleRadarPush(state, false, faultyStorage);
|
||
expect(result).toBe(false);
|
||
expect(state.notifications.radarPush).toBe(false);
|
||
expect(state.errorMessage).toContain('QuotaExceededError');
|
||
});
|
||
});
|
||
|
||
describe('3. 首选语言选择与记忆持久化 (setPreferredLocale)', () => {
|
||
it('3.1 切换首选语言,正确持久化并调用全局 setAppLocale', () => {
|
||
const {
|
||
loadProfileSettings,
|
||
setPreferredLocale,
|
||
PORTAL_LOCALE_STORAGE_KEY,
|
||
} = loadUseProfile();
|
||
const state = loadProfileSettings();
|
||
|
||
const nextLocale = setPreferredLocale(state, 'en-US');
|
||
expect(nextLocale).toBe('en-US');
|
||
expect(state.locale).toBe('en-US');
|
||
|
||
// 验证是否已记忆持久化到本地存储
|
||
const rawStored = mockStorage[PORTAL_LOCALE_STORAGE_KEY];
|
||
expect(rawStored).toBe('en-US');
|
||
});
|
||
|
||
it('3.2 防御保护:非法语言代码安全回退并归一化', () => {
|
||
const { loadProfileSettings, setPreferredLocale } = loadUseProfile();
|
||
const state = loadProfileSettings();
|
||
|
||
const safeLocale = setPreferredLocale(state, 'invalid_code');
|
||
expect(['zh-CN', 'en-US']).toContain(safeLocale);
|
||
expect(state.locale).toBe(safeLocale);
|
||
});
|
||
});
|
||
|
||
describe('4. 账户资料更新与防御保护 (updateAccount & saveProfileSettings)', () => {
|
||
it('4.1 更新名片主账户信息并持久化', () => {
|
||
const {
|
||
loadProfileSettings,
|
||
updateAccount,
|
||
saveProfileSettings,
|
||
PROFILE_ACCOUNT_STORAGE_KEY,
|
||
} = loadUseProfile();
|
||
const state = loadProfileSettings();
|
||
|
||
updateAccount(state, {
|
||
name: ' Elena Rostova ',
|
||
company: 'Northwind Ltd',
|
||
department: 'Marketing',
|
||
});
|
||
|
||
expect(state.account.name).toBe('Elena Rostova');
|
||
expect(state.account.company).toBe('Northwind Ltd');
|
||
expect(state.account.department).toBe('Marketing');
|
||
|
||
const saved = saveProfileSettings(state);
|
||
expect(saved).toBe(true);
|
||
|
||
const parsed = JSON.parse(
|
||
mockStorage[PROFILE_ACCOUNT_STORAGE_KEY] as string
|
||
) as UserProfileAccount;
|
||
expect(parsed.name).toBe('Elena Rostova');
|
||
expect(parsed.company).toBe('Northwind Ltd');
|
||
});
|
||
|
||
it('4.2 防御保护:空值或非法输入不覆盖已有有效信息', () => {
|
||
const { loadProfileSettings, updateAccount } = loadUseProfile();
|
||
const state = loadProfileSettings();
|
||
updateAccount(state, {
|
||
name: 'Elena',
|
||
company: 'Northwind Ltd',
|
||
department: 'Sales',
|
||
});
|
||
|
||
updateAccount(state, {
|
||
name: ' ',
|
||
company: '',
|
||
department: '',
|
||
});
|
||
|
||
// 空白字符串不应覆盖已有名称与企业
|
||
expect(state.account.name).toBe('Elena');
|
||
expect(state.account.company).toBe('Northwind Ltd');
|
||
expect(state.account.department).toBe('Sales');
|
||
});
|
||
});
|
||
|
||
describe('5. 商业化配额计算与现代化 UX 词条 (calculateQuotaPercent & resolveProfileText)', () => {
|
||
it('5.1 calculateQuotaPercent 正确计算百分比并处理极值边界', () => {
|
||
const { calculateQuotaPercent } = loadUseProfile();
|
||
expect(calculateQuotaPercent(0, 5)).toBe(0);
|
||
expect(calculateQuotaPercent(2, 5)).toBe(40);
|
||
expect(calculateQuotaPercent(5, 5)).toBe(100);
|
||
expect(calculateQuotaPercent(6, 5)).toBe(100); // 溢出截断
|
||
expect(calculateQuotaPercent(1, 0)).toBe(100); // 防除以0
|
||
expect(calculateQuotaPercent(-1, 5)).toBe(0); // 负数防卫
|
||
});
|
||
|
||
it('5.2 resolveProfileText 包含商业化套餐与微指标新词条', () => {
|
||
const { resolveProfileText } = loadUseProfile();
|
||
expect(resolveProfileText('tierPro')).toBeTruthy();
|
||
expect(resolveProfileText('previewPortal')).toBeTruthy();
|
||
expect(resolveProfileText('metricsCards')).toBeTruthy();
|
||
expect(resolveProfileText('metricsVisitors')).toBeTruthy();
|
||
expect(resolveProfileText('metricsLeads')).toBeTruthy();
|
||
expect(resolveProfileText('upgradeQuota')).toBeTruthy();
|
||
expect(resolveProfileText('confirmLogoutTitle')).toBeTruthy();
|
||
});
|
||
|
||
it('5.3 displayOrgField:缺失或空串走 i18n 空态,有值原样返回', () => {
|
||
const { displayOrgField, resolveProfileText } = loadUseProfile();
|
||
const emptyText = resolveProfileText('empty');
|
||
expect(emptyText).toBeTruthy();
|
||
expect(displayOrgField('')).toBe(emptyText);
|
||
expect(displayOrgField(' ')).toBe(emptyText);
|
||
expect(displayOrgField(null)).toBe(emptyText);
|
||
expect(displayOrgField(undefined)).toBe(emptyText);
|
||
expect(displayOrgField('Acme Corp')).toBe('Acme Corp');
|
||
});
|
||
});
|
||
|
||
describe('6. R39 公司/部门仅来自接口 (loadOwnerProfileAccount)', () => {
|
||
it('6.1 成功时写入接口 company / department,缺字段为空串', async () => {
|
||
const { createProfileState, loadOwnerProfileAccount } = loadUseProfile();
|
||
const state = createProfileState();
|
||
state.account.company = 'stale-company';
|
||
state.account.department = 'stale-dept';
|
||
|
||
const fetchOwnerCardPage = jest.fn(
|
||
async (): Promise<OwnerPageData<CardProfileFixture>> => ({
|
||
list: [
|
||
{
|
||
id: 8,
|
||
name: 'Elena',
|
||
company: 'Acme Corp',
|
||
},
|
||
],
|
||
pagination: { page: 1, size: 1, total: 1 },
|
||
})
|
||
);
|
||
|
||
const ok = await loadOwnerProfileAccount(state, { fetchOwnerCardPage });
|
||
expect(ok).toBe(true);
|
||
expect(fetchOwnerCardPage).toHaveBeenCalledTimes(1);
|
||
expect(state.account.company).toBe('Acme Corp');
|
||
expect(state.account.department).toBe('');
|
||
});
|
||
|
||
it('6.1b 接口同时返回 company 与 department 时原样写入', async () => {
|
||
const { createProfileState, loadOwnerProfileAccount } = loadUseProfile();
|
||
const state = createProfileState();
|
||
|
||
await loadOwnerProfileAccount(state, {
|
||
fetchOwnerCardPage: async () => ({
|
||
list: [
|
||
{
|
||
id: 3,
|
||
name: 'Elena',
|
||
company: 'Acme Corp',
|
||
department: 'APAC Sales',
|
||
},
|
||
],
|
||
pagination: { page: 1, size: 1, total: 1 },
|
||
}),
|
||
});
|
||
|
||
expect(state.account.company).toBe('Acme Corp');
|
||
expect(state.account.department).toBe('APAC Sales');
|
||
});
|
||
|
||
it('6.2 接口返回空串时保持空串,不回填示例组织', async () => {
|
||
const { createProfileState, loadOwnerProfileAccount, displayOrgField, resolveProfileText } =
|
||
loadUseProfile();
|
||
const state = createProfileState();
|
||
state.account.company = 'stale';
|
||
state.account.department = 'stale';
|
||
|
||
await loadOwnerProfileAccount(state, {
|
||
fetchOwnerCardPage: async () => ({
|
||
list: [
|
||
{
|
||
id: 1,
|
||
name: 'Elena',
|
||
company: '',
|
||
department: ' ',
|
||
},
|
||
],
|
||
pagination: { page: 1, size: 1, total: 1 },
|
||
}),
|
||
});
|
||
|
||
expect(state.account.company).toBe('');
|
||
expect(state.account.department).toBe('');
|
||
expect(displayOrgField(state.account.company)).toBe(resolveProfileText('empty'));
|
||
expect(displayOrgField(state.account.department)).toBe(resolveProfileText('empty'));
|
||
expect(resolveProfileText('empty')).toBe('暂无数据');
|
||
});
|
||
|
||
it('6.3 接口失败时清空公司与部门,不保留上次或示例值', async () => {
|
||
const { createProfileState, loadOwnerProfileAccount, clearOrgFields } = loadUseProfile();
|
||
const state = createProfileState();
|
||
state.account.company = 'stale-company';
|
||
state.account.department = 'stale-dept';
|
||
|
||
const ok = await loadOwnerProfileAccount(state, {
|
||
fetchOwnerCardPage: async () => {
|
||
throw new Error('network down');
|
||
},
|
||
});
|
||
|
||
expect(ok).toBe(false);
|
||
expect(state.account.company).toBe('');
|
||
expect(state.account.department).toBe('');
|
||
clearOrgFields(state);
|
||
expect(state.account.company).toBe('');
|
||
expect(state.account.department).toBe('');
|
||
});
|
||
|
||
it('6.4 列表为空时同样清空公司与部门', async () => {
|
||
const { createProfileState, loadOwnerProfileAccount } = loadUseProfile();
|
||
const state = createProfileState();
|
||
state.account.company = 'stale-company';
|
||
state.account.department = 'stale-dept';
|
||
|
||
const ok = await loadOwnerProfileAccount(state, {
|
||
fetchOwnerCardPage: async () => ({
|
||
list: [],
|
||
pagination: { page: 1, size: 1, total: 0 },
|
||
}),
|
||
});
|
||
|
||
expect(ok).toBe(true);
|
||
expect(state.account.company).toBe('');
|
||
expect(state.account.department).toBe('');
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('OmniCard AI 名片主个人中心视图契约 (index.uvue & pages.json)', () => {
|
||
it('页面文件与逻辑 Composable 文件均存在', () => {
|
||
expect(fs.existsSync(PROFILE_PAGE)).toBe(true);
|
||
expect(fs.existsSync(PROFILE_UTS)).toBe(true);
|
||
});
|
||
|
||
it('页面视图包含 Switch 开关,触控区 >= 44x44,且引用 Token 与 useProfile', () => {
|
||
const pageSource = fs.readFileSync(PROFILE_PAGE, 'utf8');
|
||
const logicSource = fs.readFileSync(PROFILE_UTS, 'utf8');
|
||
const combined = `${pageSource}\n${logicSource}`;
|
||
|
||
expect(pageSource.includes('script setup lang="uts"')).toBe(true);
|
||
expect(pageSource.includes('<switch')).toBe(true);
|
||
expect(pageSource.includes('@change')).toBe(true);
|
||
expect(
|
||
pageSource.includes('radarPush') || logicSource.includes('radarPush')
|
||
).toBe(true);
|
||
expect(
|
||
pageSource.includes('leadNotice') || logicSource.includes('leadNotice')
|
||
).toBe(true);
|
||
expect(combined.includes('loadProfileSettings')).toBe(true);
|
||
expect(combined.includes('toggleRadarPush')).toBe(true);
|
||
expect(combined.includes('toggleLeadNotice')).toBe(true);
|
||
expect(combined.includes('setPreferredLocale')).toBe(true);
|
||
expect(combined.includes('loadOwnerProfileAccount')).toBe(true);
|
||
expect(combined.includes('displayOrgField')).toBe(true);
|
||
|
||
// OmniCard 设计 Token
|
||
expect(pageSource.includes('#0088FF')).toBe(true); // primary
|
||
expect(pageSource.includes('#F5F8FC')).toBe(true); // bg
|
||
expect(pageSource.includes('#101828')).toBe(true); // text
|
||
expect(pageSource.includes('#64748B')).toBe(true); // text-2
|
||
expect(pageSource.includes('#EBF1F7')).toBe(true); // border
|
||
|
||
// Switch 触控区 >= 44x44 规范验证 (min-height >= 44px 或 height >= 44px)
|
||
expect(
|
||
pageSource.includes('min-height: 44px') ||
|
||
pageSource.includes('min-height: 48px') ||
|
||
pageSource.includes('height: 48px') ||
|
||
pageSource.includes('height: 44px') ||
|
||
pageSource.includes('switch-touch-area')
|
||
).toBe(true);
|
||
});
|
||
|
||
it('零 emoji、无 any,公司/部门无写死示例组织', () => {
|
||
const pageSource = fs.readFileSync(PROFILE_PAGE, 'utf8');
|
||
const logicSource = fs.readFileSync(PROFILE_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);
|
||
|
||
// 公司/部门展示走 displayOrgField,禁止写死示例组织
|
||
expect(template.includes('displayOrgField')).toBe(true);
|
||
expect(template.includes('state.account.company }}')).toBe(false);
|
||
expect(template.includes('state.account.department }}')).toBe(false);
|
||
expect(SAMPLE_ORG_RE.test(pageSource)).toBe(false);
|
||
expect(SAMPLE_ORG_RE.test(logicSource)).toBe(false);
|
||
expect(logicSource.includes("company: 'Nexus")).toBe(false);
|
||
expect(logicSource.includes('company: "Nexus')).toBe(false);
|
||
expect(logicSource.includes('Overseas Business')).toBe(false);
|
||
expect(logicSource.includes("resolveProfileText('empty')")).toBe(true);
|
||
expect(logicSource.includes("getOmniText(locale, 'common.empty')")).toBe(true);
|
||
});
|
||
|
||
it('mpuniapp/pages.json 中已成功注册 pages/owner/profile/index', () => {
|
||
expect(fs.existsSync(PAGES_JSON)).toBe(true);
|
||
const pagesJsonContent = fs.readFileSync(PAGES_JSON, 'utf8');
|
||
const cleanJson = pagesJsonContent
|
||
.replace(/\/\*[\s\S]*?\*\//g, '')
|
||
.replace(/\/\/[^\n\r]*/g, '');
|
||
let found = false;
|
||
try {
|
||
const pagesConfig = JSON.parse(cleanJson);
|
||
found = pagesConfig.pages.some(
|
||
(p: { path: string }) => p.path === 'pages/owner/profile/index'
|
||
);
|
||
} catch (_e) {
|
||
found = pagesJsonContent.includes('"path": "pages/owner/profile/index"');
|
||
}
|
||
expect(found).toBe(true);
|
||
});
|
||
});
|