import { describe, it, expect, beforeEach } from 'vitest' import * as fc from 'fast-check' import { createI18n } from 'vue-i18n' import zhCN from '../i18n/zh-CN.js' import zhTW from '../i18n/zh-TW.js' import en from '../i18n/en.js' import { SUPPORTED_LOCALES } from '../i18n/index.js' import { setStorage, getStorage, LOCALE_KEY } from '../utils/storage.js' // 所有语言包 const messages = { 'zh-CN': zhCN, 'zh-TW': zhTW, 'en': en } /** * 递归收集所有翻译键(扁平化为 "a.b.c" 格式) */ function collectKeys(obj, prefix = '') { let keys = [] for (const key of Object.keys(obj)) { const fullKey = prefix ? `${prefix}.${key}` : key if (typeof obj[key] === 'object' && obj[key] !== null) { keys = keys.concat(collectKeys(obj[key], fullKey)) } else { keys.push(fullKey) } } return keys } /** * 根据扁平键从对象中取值 */ function getNestedValue(obj, path) { return path.split('.').reduce((acc, part) => acc && acc[part], obj) } // 收集所有翻译键(以zh-CN为基准) const allKeys = collectKeys(zhCN) describe('Property 1: 语言切换翻译正确性', () => { /** * **Feature: mobile-app, Property 1: 语言切换翻译正确性** * **Validates: Requirements 1.2** * * 对于任意支持的语言locale和任意翻译键key,切换到该locale后, * 翻译函数t(key)应返回该语言对应的非空文本。 */ it('切换到任意支持语言后,翻译函数返回对应的非空文本', () => { const localeArb = fc.constantFrom(...SUPPORTED_LOCALES) const keyArb = fc.constantFrom(...allKeys) fc.assert( fc.property(localeArb, keyArb, (locale, key) => { const i18n = createI18n({ legacy: false, locale, messages }) const result = i18n.global.t(key) // 翻译结果应为非空字符串 expect(typeof result).toBe('string') expect(result.length).toBeGreaterThan(0) // 翻译结果应等于该语言包中的原始值(不含插值参数的情况下) const expected = getNestedValue(messages[locale], key) if (expected !== undefined && !String(expected).includes('{')) { expect(result).toBe(String(expected)) } }), { numRuns: 200 } ) }) }) describe('Property 2: 语言设置持久化Round-Trip', () => { /** * **Feature: mobile-app, Property 2: 语言设置持久化Round-Trip** * **Validates: Requirements 1.4, 1.5** * * 对于任意支持的语言locale,设置语言后持久化到本地存储, * 再从本地存储读取并初始化,最终的locale应等于最初设置的值。 */ let mockStorage = {} beforeEach(() => { mockStorage = {} globalThis.uni = { setStorageSync: (key, value) => { mockStorage[key] = value }, getStorageSync: (key) => mockStorage[key] || '', removeStorageSync: (key) => { delete mockStorage[key] }, getSystemInfoSync: () => ({ language: 'zh-CN' }) } }) it('设置语言后持久化再读取,locale保持一致', () => { const localeArb = fc.constantFrom(...SUPPORTED_LOCALES) fc.assert( fc.property(localeArb, (targetLocale) => { // 步骤1:持久化语言设置 setStorage(LOCALE_KEY, targetLocale) // 步骤2:从存储中读取 const restored = getStorage(LOCALE_KEY) // 验证:读取的值应等于设置的值 expect(restored).toBe(targetLocale) }), { numRuns: 100 } ) }) })