/** * 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; }; 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; }; 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 }>; }; 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(); (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'); }); it('TabBar 5大菜单 key 在七语均有对应本地化文案,且支持 updateTabBarTitles()', () => { const setTabBarItemMock = jest.fn(); (global as { uni?: unknown }).uni = { setTabBarItem: setTabBarItemMock, getStorageSync: () => 'en-US', setStorageSync: () => {}, $emit: () => {}, }; const { omniLocales, updateTabBarTitles } = require(LOCALE_UTS) as { omniLocales: Record }>; updateTabBarTitles: (locale?: string) => void; }; const tabKeys = ['tab.workplace', 'tab.card', 'tab.radar', 'tab.leads', 'tab.profile']; for (const locale of ['zh-CN', 'en-US', 'ar-SA', 'es-ES', 'de-DE', 'fr-FR', 'ja-JP']) { for (const key of tabKeys) { const text = omniLocales[locale].messages[key]; expect(typeof text).toBe('string'); expect(text.length).toBeGreaterThan(0); } } setTabBarItemMock.mockClear(); updateTabBarTitles('en-US'); expect(setTabBarItemMock).toHaveBeenCalledTimes(5); expect(setTabBarItemMock).toHaveBeenNthCalledWith(1, { index: 0, text: 'Workplace' }); expect(setTabBarItemMock).toHaveBeenNthCalledWith(2, { index: 1, text: 'Card' }); expect(setTabBarItemMock).toHaveBeenNthCalledWith(3, { index: 2, text: 'Radar' }); expect(setTabBarItemMock).toHaveBeenNthCalledWith(4, { index: 3, text: 'Leads' }); expect(setTabBarItemMock).toHaveBeenNthCalledWith(5, { index: 4, text: 'Profile' }); }); });