115 lines
3.5 KiB
JavaScript
115 lines
3.5 KiB
JavaScript
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 }
|
||
)
|
||
})
|
||
})
|