110 lines
4.0 KiB
JavaScript
110 lines
4.0 KiB
JavaScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||
import { createTestPinia } from '../helpers/app'
|
||
import { useI18nStore } from '@/stores/i18n'
|
||
import { BUILTIN_MESSAGES } from '@/i18n/builtin-messages'
|
||
|
||
const loadDataApi = vi.fn()
|
||
|
||
vi.mock('@/services/api', () => ({
|
||
loadDataApi: (...args) => loadDataApi(...args),
|
||
}))
|
||
|
||
/** 按 s_i18n 行结构构造返回值 */
|
||
function rows(locale, entries) {
|
||
return {
|
||
data: entries.map(([b_key, b_value]) => ({ b_key, b_locale: locale, b_value, b_canuse: 1 })),
|
||
}
|
||
}
|
||
|
||
beforeEach(() => {
|
||
loadDataApi.mockReset()
|
||
createTestPinia()
|
||
const i18n = useI18nStore()
|
||
i18n.setLocale('zh-CN')
|
||
i18n.clearMessages()
|
||
})
|
||
|
||
describe('i18n store · 译文加载(s_i18n)', () => {
|
||
it('加载当前语言译文并写入运行时(t 生效)', async () => {
|
||
loadDataApi.mockResolvedValue(rows('zh-CN', [['field.x.b_name', '客户名称']]))
|
||
const i18n = useI18nStore()
|
||
|
||
await i18n.loadMessages()
|
||
|
||
expect(loadDataApi).toHaveBeenCalledTimes(1)
|
||
const [viewName, condition] = loadDataApi.mock.calls[0]
|
||
expect(viewName).toBe('s_i18n')
|
||
expect(condition).toContain("b_locale = N'zh-CN'")
|
||
expect(condition).toContain('b_canuse = 1')
|
||
expect(i18n.t('field.x.b_name', '名称')).toBe('客户名称')
|
||
})
|
||
|
||
it('英文模式:内置 UI 文案兜底,库表译文覆盖同名 key', async () => {
|
||
loadDataApi.mockResolvedValue(rows('en-US', [['common.cancel', 'Cancel!!']]))
|
||
const i18n = useI18nStore()
|
||
i18n.setLocale('en-US')
|
||
|
||
await i18n.loadMessages()
|
||
|
||
// 库里有的以库为准
|
||
expect(i18n.t('common.cancel', '取消')).toBe('Cancel!!')
|
||
// 库里没有的走前端内置英文
|
||
expect(i18n.t('common.applyQuery', '应用')).toBe(BUILTIN_MESSAGES['en-US']['common.applyQuery'])
|
||
expect(i18n.t('common.applyQuery', '应用')).toBe('Apply')
|
||
})
|
||
|
||
it('切换语言后重新加载,且缓存命中不再发请求', async () => {
|
||
loadDataApi.mockResolvedValue(rows('zh-CN', [['k', '中文']]))
|
||
const i18n = useI18nStore()
|
||
await i18n.loadMessages()
|
||
expect(loadDataApi).toHaveBeenCalledTimes(1)
|
||
|
||
// 切到英文:新语言未缓存 → 发请求
|
||
loadDataApi.mockResolvedValue(rows('en-US', [['k', 'EN']]))
|
||
i18n.setLocale('en-US')
|
||
await i18n.loadMessages('en-US')
|
||
expect(loadDataApi).toHaveBeenCalledTimes(2)
|
||
expect(i18n.t('k', 'x')).toBe('EN')
|
||
|
||
// 切回中文:命中缓存 → 不再请求,但译文要切回来
|
||
i18n.setLocale('zh-CN')
|
||
await i18n.loadMessages()
|
||
expect(loadDataApi).toHaveBeenCalledTimes(2)
|
||
expect(i18n.t('k', 'x')).toBe('中文')
|
||
})
|
||
|
||
it('并发同语言只发一个请求', async () => {
|
||
loadDataApi.mockResolvedValue(rows('zh-CN', [['k', 'v']]))
|
||
const i18n = useI18nStore()
|
||
await Promise.all([i18n.loadMessages(), i18n.loadMessages(), i18n.loadMessages()])
|
||
expect(loadDataApi).toHaveBeenCalledTimes(1)
|
||
})
|
||
|
||
it('接口失败不抛出,界面回落兜底文案', async () => {
|
||
loadDataApi.mockRejectedValue(new Error('boom'))
|
||
const i18n = useI18nStore()
|
||
await expect(i18n.loadMessages()).resolves.toEqual({})
|
||
expect(i18n.t('common.cancel', '取消')).toBe('取消')
|
||
expect(i18n.error).toBe('boom')
|
||
})
|
||
|
||
it('b_locale 走转义,单引号不会拼坏 SQL', async () => {
|
||
loadDataApi.mockResolvedValue({ data: [] })
|
||
const i18n = useI18nStore()
|
||
await i18n.loadMessages("zh'; drop table s_i18n --")
|
||
expect(loadDataApi.mock.calls[0][1]).toContain("N'zh''; drop table s_i18n --'")
|
||
})
|
||
|
||
it('请求回来时语言已被切走则不覆盖当前译文', async () => {
|
||
let resolveQuery
|
||
loadDataApi.mockReturnValue(new Promise((r) => (resolveQuery = r)))
|
||
const i18n = useI18nStore()
|
||
const pending = i18n.loadMessages()
|
||
i18n.setLocale('en-US')
|
||
resolveQuery({ data: [{ b_key: 'k', b_value: '中文值' }] })
|
||
await pending
|
||
// 当前是英文,中文译文不应写进运行时(英文下 k 无译文 → 回落 fallback)
|
||
expect(i18n.t('k', 'fallback')).toBe('fallback')
|
||
})
|
||
})
|