Newer
Older
navi-1 / webclient / tests / unit / components / settings / accountPanel.test.js
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')
  })
})