Files

263 lines
7.7 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { mock, type MockProxy } from 'vitest-mock-extended';
import type { HomeAssistant } from '../../src/ha/types';
// localize.ts stores the active language and loaded translations in module-level
// variables. resetModules() clears Vitest's module cache so each test gets a
// fresh import with those variables re-initialized.
const importFresh = async () => {
vi.resetModules();
return await import('../../src/localize/localize');
};
const stubGlobals = (): MockProxy<Storage> => {
const storage = mock<Storage>();
storage.getItem.mockReturnValue(null);
vi.stubGlobal('localStorage', storage);
vi.stubGlobal('navigator', { languages: [] });
return storage;
};
describe('getLanguage', () => {
let storage: MockProxy<Storage>;
beforeEach(() => {
storage = stubGlobals();
});
afterEach(() => {
vi.unstubAllGlobals();
});
it('should return hass.language when available', async () => {
const { getLanguage } = await importFresh();
const hass = mock<HomeAssistant>();
hass.language = 'fr';
hass.selectedLanguage = null;
expect(getLanguage(hass)).toBe('fr');
});
it('should return hass.selectedLanguage when language is not set', async () => {
const { getLanguage } = await importFresh();
const hass = mock<HomeAssistant>();
// language must be nullish (not empty string) for selectedLanguage fallback.
hass.language = undefined as unknown as string;
hass.selectedLanguage = 'de';
expect(getLanguage(hass)).toBe('de');
});
it('should canonicalize hyphens to underscores', async () => {
const { getLanguage } = await importFresh();
const hass = mock<HomeAssistant>();
hass.language = 'pt-BR';
expect(getLanguage(hass)).toBe('pt_BR');
});
it('should fall back to localStorage selectedLanguage', async () => {
const { getLanguage } = await importFresh();
storage.getItem.mockReturnValue(JSON.stringify('it'));
expect(getLanguage()).toBe('it');
});
it('should canonicalize localStorage language', async () => {
const { getLanguage } = await importFresh();
storage.getItem.mockReturnValue(JSON.stringify('pt-BR'));
expect(getLanguage()).toBe('pt_BR');
});
it('should skip null localStorage value', async () => {
const { getLanguage } = await importFresh();
storage.getItem.mockReturnValue(JSON.stringify(null));
expect(getLanguage()).toBe('en');
});
it('should fall back to navigator.languages when matching a loaded language', async () => {
vi.stubGlobal('navigator', { languages: ['en'] });
const { getLanguage } = await importFresh();
expect(getLanguage()).toBe('en');
});
it('should skip non-matching navigator languages', async () => {
vi.stubGlobal('navigator', { languages: ['xx', 'yy'] });
const { getLanguage } = await importFresh();
expect(getLanguage()).toBe('en');
});
it('should return default language when no sources available', async () => {
const { getLanguage } = await importFresh();
expect(getLanguage()).toBe('en');
});
});
describe('loadLanguages', () => {
beforeEach(() => {
stubGlobals();
});
afterEach(() => {
vi.unstubAllGlobals();
});
it('should load and use Catalan translations', async () => {
const { loadLanguages, localize } = await importFresh();
const hass = mock<HomeAssistant>();
hass.language = 'ca';
await loadLanguages(hass);
expect(localize('common.no_media')).toBe('No hi ha suport per mostrar');
});
it('should load and use German translations', async () => {
const { loadLanguages, localize } = await importFresh();
const hass = mock<HomeAssistant>();
hass.language = 'de';
await loadLanguages(hass);
expect(localize('actions.abort')).toBe('Aktion abbrechen');
});
it('should load and use French translations', async () => {
const { loadLanguages, localize } = await importFresh();
const hass = mock<HomeAssistant>();
hass.language = 'fr';
await loadLanguages(hass);
expect(localize('common.no_media')).toBe('Aucun média à afficher');
});
it('should load and use Italian translations', async () => {
const { loadLanguages, localize } = await importFresh();
const hass = mock<HomeAssistant>();
hass.language = 'it';
await loadLanguages(hass);
expect(localize('common.no_media')).toBe(
'Nessun contenuto multimediale da visualizzare',
);
});
it('should load and use Polish translations', async () => {
const { loadLanguages, localize } = await importFresh();
const hass = mock<HomeAssistant>();
hass.language = 'pl';
await loadLanguages(hass);
expect(localize('actions.abort')).toBe('Przerwano akcję');
});
it('should load and use Portuguese translations', async () => {
const { loadLanguages, localize } = await importFresh();
const hass = mock<HomeAssistant>();
hass.language = 'pt';
await loadLanguages(hass);
expect(localize('common.no_media')).toBe('Sem média');
});
it('should load and use Brazilian Portuguese translations', async () => {
const { loadLanguages, localize } = await importFresh();
const hass = mock<HomeAssistant>();
hass.language = 'pt_BR';
hass.selectedLanguage = null;
await loadLanguages(hass);
expect(localize('common.no_media')).toBe('Nenhuma mídia para exibir');
});
it('should fall back to English for unsupported language', async () => {
const { loadLanguages, localize } = await importFresh();
const hass = mock<HomeAssistant>();
hass.language = 'xx';
await loadLanguages(hass);
expect(localize('common.advanced_camera_card')).toBe('Advanced Camera Card');
});
});
describe('localize', () => {
beforeEach(() => {
stubGlobals();
vi.stubGlobal('navigator', { languages: [] });
});
afterEach(() => {
vi.unstubAllGlobals();
});
it('should resolve a dotted key from English', async () => {
const { localize } = await importFresh();
expect(localize('common.advanced_camera_card')).toBe('Advanced Camera Card');
});
it('should use loaded language when available', async () => {
const { loadLanguages, localize } = await importFresh();
const hass = mock<HomeAssistant>();
hass.language = 'de';
await loadLanguages(hass);
// German translation should be used (and it exists).
const result = localize('common.advanced_camera_card');
expect(result).toBeTruthy();
});
it('should fall back to English when primary language key is missing', async () => {
const { loadLanguages, localize } = await importFresh();
const hass = mock<HomeAssistant>();
hass.language = 'de';
await loadLanguages(hass);
// 'common.no_media' exists only in English, so the German lookup throws
// and falls back to English.
expect(localize('common.no_media')).toBe('No media to display');
});
it('should perform search and replace', async () => {
const { localize } = await importFresh();
const result = localize('common.advanced_camera_card', 'Advanced', 'Basic');
expect(result).toBe('Basic Camera Card');
});
it('should not replace when search is empty', async () => {
const { localize } = await importFresh();
expect(localize('common.advanced_camera_card', '', '')).toBe('Advanced Camera Card');
});
it('should return the key for completely missing key', async () => {
const { localize } = await importFresh();
expect(localize('nonexistent.deeply.nested.key')).toBe(
'nonexistent.deeply.nested.key',
);
});
it('should return translated value for single-segment key', async () => {
const { localize } = await importFresh();
// 'actions' is a top-level key that resolves to an object, not a string,
// but should not throw.
expect(localize('actions')).toBeTruthy();
});
});