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') }) })