import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import { nextTick } from 'vue'
import { createPinia, setActivePinia } from 'pinia'
import { useAuthStore } from '@/stores/auth.js'
import { LOCALE_STORAGE_KEY, locale, setLocale } from '@/i18n/index.js'
import AccountPanel from '@/components/settings/AccountPanel.vue'
vi.mock('@/api', () => ({ updateMe: vi.fn() }))
vi.mock('@/components/settings/ApiKeysPanel.vue', () => ({
default: { name: 'ApiKeysPanel', template: '<div data-test="keys-stub" />' },
}))
// No GnToastProvider in tests — swallow toast calls.
vi.mock('gnexus-ui-kit/vue', async (importOriginal) => {
const mod = await importOriginal()
const toast = {
show: () => {}, info: () => {}, success: () => {},
warning: () => {}, danger: () => {}, error: () => {}, close: () => {},
}
return { ...mod, useToast: () => toast }
})
import { updateMe } from '@/api'
/** The language picker of a signed-in user whose account speaks uk. */
function signedIn({ stored = null, effective = 'uk' } = {}) {
const auth = useAuthStore()
auth.authEnabled = true
auth.user = { id: 'u1', locale: stored, locale_effective: effective }
return auth
}
function options(wrapper) {
return wrapper.findAll('option').map(option => option.text())
}
async function select(wrapper, value) {
const el = wrapper.find('.language-select')
await el.setValue(value)
await flushPromises()
}
describe('Account — interface language', () => {
beforeEach(() => {
setActivePinia(createPinia())
setLocale('en')
document.documentElement.lang = 'en'
updateMe.mockReset()
})
afterEach(() => {
vi.unstubAllGlobals()
})
// The label of a language is written in that language, so the same three
// strings appear in every dictionary and are never translated.
it('lists the account option first, then every language in its own name', () => {
signedIn()
const wrapper = mount(AccountPanel)
expect(options(wrapper)).toEqual(['Auto (from account)', 'English', 'Українська', 'Русский'])
expect(wrapper.find('[data-test="keys-stub"]').exists()).toBe(true)
})
it('shows what the interface actually uses right now', () => {
signedIn({ stored: 'ru', effective: 'ru' })
expect(mount(AccountPanel).find('.language-select').element.value).toBe('ru')
})
it('shows Auto when the account language is what is used', () => {
signedIn({ stored: null, effective: 'uk' })
expect(mount(AccountPanel).find('.language-select').element.value).toBe('')
})
it('stores a chosen language on the account and switches to it', async () => {
const auth = signedIn()
updateMe.mockResolvedValue({ id: 'u1', locale: 'ru', locale_effective: 'ru' })
const wrapper = mount(AccountPanel)
await select(wrapper, 'ru')
expect(updateMe).toHaveBeenCalledWith({ locale: 'ru' })
expect(auth.user.locale).toBe('ru')
expect(locale.value).toBe('ru')
await nextTick()
expect(document.documentElement.lang).toBe('ru')
})
it('sends Auto as "auto" and goes back to the account language', async () => {
const auth = signedIn({ stored: 'ru', effective: 'ru' })
updateMe.mockResolvedValue({ id: 'u1', locale: null, locale_effective: 'uk' })
const wrapper = mount(AccountPanel)
await select(wrapper, '')
expect(updateMe).toHaveBeenCalledWith({ locale: 'auto' })
expect(auth.user.locale).toBeNull()
expect(locale.value).toBe('uk')
})
// A save that failed must not look saved: the picker keeps naming the
// language the backend actually holds.
it('keeps the current language when the choice cannot be stored', async () => {
signedIn({ stored: 'ru', effective: 'ru' })
setLocale('ru') // what boot did with locale_effective
updateMe.mockRejectedValue(new Error('PATCH /auth/me → 503'))
const wrapper = mount(AccountPanel)
await select(wrapper, 'en')
expect(locale.value).toBe('ru')
expect(wrapper.find('.language-select').element.value).toBe('ru')
})
// Sign-in switched off: there is no account to follow, so Auto is not
// offered and the choice is this browser's.
it('offers an explicit choice only when there is no account', async () => {
const auth = useAuthStore()
auth.authEnabled = false
auth.user = null
const storage = { entries: {}, getItem(k) { return this.entries[k] ?? null }, setItem(k, v) { this.entries[k] = v } }
vi.stubGlobal('localStorage', storage)
const wrapper = mount(AccountPanel)
expect(options(wrapper)).toEqual(['English', 'Українська', 'Русский'])
await select(wrapper, 'ru')
expect(updateMe).not.toHaveBeenCalled()
expect(storage.entries[LOCALE_STORAGE_KEY]).toBe('ru')
expect(locale.value).toBe('ru')
})
})