- 模块一: 出海案例作品集、博客动态发布、人名发音语音条、RTL阿语多语言切换 - 模块二: AI顾问人设配置、行业术语库润色、RAG切片沙盒调试、流式会话与意向留资卡片 - 模块三: 全球访客雷达大屏、多维加权意向算分、外部CRM对接推送、团队公海池流转 - 模块四: 移动端NFC安全挂失、NFC动态路由规则配置、动态防伪验签、展会制卡Zip批量导出 - 模块五: 组织架构与名片分配、移动端个人中心设置、离职交接与历史线索迁移、安全审计日志 - 模块六: Stripe国际信用卡订阅Webhook、独立二级域名与SSL检测、坐席配额预警、多币种发票 - 模块七: 纸质名片OCR解析入库、国际商务日程RFC 5545 ICS日历、微官网离线SWR预加载、展会实时排行榜大屏 - 模块八: 全球出海SEO OpenGraph元标签注入与社交爬虫探测、30点端到端全链路业务流集成测试套件
183 行
6.9 KiB
TypeScript
183 行
6.9 KiB
TypeScript
/**
|
||
* OmniCard AI (CRMmp) - 访客端门户 i18n 词典与 RTL 契约测试 (TDD Red -> Green)
|
||
*/
|
||
|
||
import * as fs from 'fs';
|
||
import * as path from 'path';
|
||
|
||
const LOCALE_UTS = path.resolve(__dirname, '../../../mpuniapp/common/locale.uts');
|
||
const PORTAL_UTS = path.resolve(__dirname, '../../../mpuniapp/pages/portal/usePortal.uts');
|
||
const PORTAL_INDEX = path.resolve(__dirname, '../../../mpuniapp/pages/portal/index.uvue');
|
||
|
||
describe('OmniCard AI 访客端门户多语言词典 (locale.uts)', () => {
|
||
afterEach(() => {
|
||
delete (global as { uni?: unknown }).uni;
|
||
jest.resetModules();
|
||
});
|
||
|
||
it('ar-SA.dir 应为 rtl', () => {
|
||
const { omniLocales } = require(LOCALE_UTS) as {
|
||
omniLocales: Record<string, { dir: string }>;
|
||
};
|
||
expect(omniLocales['ar-SA'].dir).toBe('rtl');
|
||
});
|
||
|
||
it("getOmniText('en-US','card.save_contact') 应为 Save Contact", () => {
|
||
const { getOmniText } = require(LOCALE_UTS) as {
|
||
getOmniText: (locale: string, key: string) => string;
|
||
};
|
||
expect(getOmniText('en-US', 'card.save_contact')).toBe('Save Contact');
|
||
});
|
||
|
||
it('es-ES.dir 应为 ltr', () => {
|
||
const { omniLocales } = require(LOCALE_UTS) as {
|
||
omniLocales: Record<string, { dir: string }>;
|
||
};
|
||
expect(omniLocales['es-ES'].dir).toBe('ltr');
|
||
});
|
||
|
||
it("getOmniText('ja-JP','card.save_contact') 非空且不是「保存到通讯录」", () => {
|
||
const { getOmniText } = require(LOCALE_UTS) as {
|
||
getOmniText: (locale: string, key: string) => string;
|
||
};
|
||
const text = getOmniText('ja-JP', 'card.save_contact');
|
||
expect(text.length).toBeGreaterThan(0);
|
||
expect(text).not.toBe('保存到通讯录');
|
||
});
|
||
|
||
it('门户新增 key 在七语均有文案', () => {
|
||
const { omniLocales } = require(LOCALE_UTS) as {
|
||
omniLocales: Record<string, { messages: Record<string, string> }>;
|
||
};
|
||
const keys = [
|
||
'portal.unavailable',
|
||
'portal.retry',
|
||
'portal.pronounce',
|
||
'portal.pronouncing',
|
||
'portal.whatsapp',
|
||
'portal.linkedin',
|
||
'portal.email',
|
||
'portal.phone',
|
||
'card.save_contact',
|
||
'portal.case',
|
||
'portal.article',
|
||
'portal.social',
|
||
'portal.video',
|
||
'portal.block',
|
||
];
|
||
for (const locale of ['zh-CN', 'en-US', 'ar-SA', 'es-ES', 'de-DE', 'fr-FR', 'ja-JP']) {
|
||
for (const key of keys) {
|
||
const text = omniLocales[locale].messages[key];
|
||
expect(typeof text).toBe('string');
|
||
expect(text.length).toBeGreaterThan(0);
|
||
}
|
||
}
|
||
});
|
||
|
||
it('语言检测:zh/ar/en 保持,es/de/fr/ja 映射到对应区域码', () => {
|
||
const { resolveDeviceLocale } = require(LOCALE_UTS) as {
|
||
resolveDeviceLocale: (raw: string) => string;
|
||
};
|
||
expect(resolveDeviceLocale('zh')).toBe('zh-CN');
|
||
expect(resolveDeviceLocale('zh-Hans')).toBe('zh-CN');
|
||
expect(resolveDeviceLocale('ar')).toBe('ar-SA');
|
||
expect(resolveDeviceLocale('ar-EG')).toBe('ar-SA');
|
||
expect(resolveDeviceLocale('en')).toBe('en-US');
|
||
expect(resolveDeviceLocale('es')).toBe('es-ES');
|
||
expect(resolveDeviceLocale('de')).toBe('de-DE');
|
||
expect(resolveDeviceLocale('fr')).toBe('fr-FR');
|
||
expect(resolveDeviceLocale('fr-FR')).toBe('fr-FR');
|
||
expect(resolveDeviceLocale('ja')).toBe('ja-JP');
|
||
expect(resolveDeviceLocale('ko')).toBe('en-US');
|
||
});
|
||
});
|
||
|
||
describe('OmniCard AI 访客端门户页文案走 i18n (index.uvue)', () => {
|
||
it('index.uvue 引用 getOmniText 或 locale,且不再硬编码保存到手机通讯录', () => {
|
||
const source = fs.readFileSync(PORTAL_INDEX, 'utf8');
|
||
expect(source.includes('getOmniText') || source.includes('locale')).toBe(true);
|
||
expect(source.includes('保存到手机通讯录')).toBe(false);
|
||
});
|
||
});
|
||
|
||
describe('门户进入页语言解析 (resolvePortalLocale)', () => {
|
||
afterEach(() => {
|
||
delete (global as { uni?: unknown }).uni;
|
||
jest.resetModules();
|
||
});
|
||
|
||
it('存储键应为 omni_locale', () => {
|
||
const { PORTAL_LOCALE_STORAGE_KEY } = require(PORTAL_UTS) as {
|
||
PORTAL_LOCALE_STORAGE_KEY: string;
|
||
};
|
||
expect(PORTAL_LOCALE_STORAGE_KEY).toBe('omni_locale');
|
||
const pageSource = fs.readFileSync(PORTAL_INDEX, 'utf8');
|
||
expect(pageSource.includes('persistPortalLocale')).toBe(true);
|
||
expect(pageSource.includes('resolvePortalLocale')).toBe(true);
|
||
expect(pageSource.includes('options.lang')).toBe(true);
|
||
expect(pageSource.includes("switchLang('es-ES')")).toBe(true);
|
||
expect(pageSource.includes("switchLang('de-DE')")).toBe(true);
|
||
expect(pageSource.includes("switchLang('fr-FR')")).toBe(true);
|
||
expect(pageSource.includes("switchLang('ja-JP')")).toBe(true);
|
||
});
|
||
|
||
it('query.lang 优先于 storage 与 suggestedLocale', () => {
|
||
const { resolvePortalLocale } = require(PORTAL_UTS) as {
|
||
resolvePortalLocale: (
|
||
queryLang: string,
|
||
stored: string,
|
||
suggested: string,
|
||
fallback: string
|
||
) => string;
|
||
};
|
||
expect(resolvePortalLocale('ar-SA', 'zh-CN', 'en-US', 'en-US')).toBe('ar-SA');
|
||
expect(resolvePortalLocale('en-US', 'zh-CN', 'ar-SA', 'zh-CN')).toBe('en-US');
|
||
});
|
||
|
||
it('无 query.lang 时使用 storage,再才是 suggestedLocale', () => {
|
||
const { resolvePortalLocale } = require(PORTAL_UTS) as {
|
||
resolvePortalLocale: (
|
||
queryLang: string,
|
||
stored: string,
|
||
suggested: string,
|
||
fallback: string
|
||
) => string;
|
||
};
|
||
expect(resolvePortalLocale('', 'en-US', 'ar-SA', 'zh-CN')).toBe('en-US');
|
||
expect(resolvePortalLocale('xx', 'en-US', 'ar-SA', 'zh-CN')).toBe('en-US');
|
||
expect(resolvePortalLocale('', '', 'ar-SA', 'zh-CN')).toBe('ar-SA');
|
||
});
|
||
|
||
it('七语白名单放行,非法值回退 fallback', () => {
|
||
const { resolvePortalLocale } = require(PORTAL_UTS) as {
|
||
resolvePortalLocale: (
|
||
queryLang: string,
|
||
stored: string,
|
||
suggested: string,
|
||
fallback: string
|
||
) => string;
|
||
};
|
||
expect(resolvePortalLocale('fr-FR', 'en-US', 'ar-SA', 'zh-CN')).toBe('fr-FR');
|
||
expect(resolvePortalLocale('ja-JP', 'xx', 'yy', 'zh-CN')).toBe('ja-JP');
|
||
expect(resolvePortalLocale('es-ES', '', '', 'en-US')).toBe('es-ES');
|
||
expect(resolvePortalLocale('de-DE', 'zh-CN', 'en-US', 'en-US')).toBe('de-DE');
|
||
expect(resolvePortalLocale('pt-BR', 'xx', 'yy', 'zh-CN')).toBe('zh-CN');
|
||
expect(resolvePortalLocale('zh', 'en', 'ar', 'en-US')).toBe('en-US');
|
||
});
|
||
|
||
it('persistPortalLocale 写入 omni_locale,readStoredPortalLocale 读回', () => {
|
||
const storageMap = new Map<string, string>();
|
||
(global as { uni?: unknown }).uni = {
|
||
getStorageSync: (key: string) => storageMap.get(key) || '',
|
||
setStorageSync: (key: string, val: string) => storageMap.set(key, val),
|
||
};
|
||
const { persistPortalLocale, readStoredPortalLocale } = require(PORTAL_UTS) as {
|
||
persistPortalLocale: (code: string) => void;
|
||
readStoredPortalLocale: () => string;
|
||
};
|
||
persistPortalLocale('en-US');
|
||
expect(storageMap.get('omni_locale')).toBe('en-US');
|
||
expect(readStoredPortalLocale()).toBe('en-US');
|
||
});
|
||
});
|