138 lines
3.9 KiB
TypeScript
138 lines
3.9 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
type StorageLike = {
|
|
getItem: (key: string) => string | null;
|
|
setItem: (key: string, value: string) => void;
|
|
};
|
|
|
|
function storageMock(initial: Record<string, string> = {}): StorageLike {
|
|
const data = new Map<string, string>(Object.entries(initial));
|
|
return {
|
|
getItem: vi.fn((key: string) => data.get(key) ?? null),
|
|
setItem: vi.fn((key: string, value: string) => {
|
|
data.set(key, value);
|
|
}),
|
|
};
|
|
}
|
|
|
|
describe('lobby i18n locale propagation', () => {
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
vi.unstubAllGlobals();
|
|
vi.resetModules();
|
|
});
|
|
|
|
it('notifies subscribers immediately and on locale changes', async () => {
|
|
const localStorage = storageMock({ 'wpp.locale': 'en' });
|
|
vi.stubGlobal('window', {
|
|
location: { search: '' },
|
|
localStorage,
|
|
});
|
|
vi.stubGlobal('navigator', { language: 'en-US' });
|
|
|
|
const i18n = await import('./lobby-i18n');
|
|
|
|
const updates: string[] = [];
|
|
const unsubscribe = i18n.subscribeToLocaleChanges((locale) => updates.push(locale));
|
|
|
|
expect(updates).toEqual(['en']);
|
|
|
|
i18n.setPreferredLocale('da');
|
|
expect(updates).toEqual(['en', 'da']);
|
|
|
|
unsubscribe();
|
|
i18n.setPreferredLocale('en');
|
|
expect(updates).toEqual(['en', 'da']);
|
|
});
|
|
|
|
it('prefers backend-provided shell locale over browser defaults', async () => {
|
|
vi.stubGlobal('window', {
|
|
location: { search: '' },
|
|
localStorage: storageMock(),
|
|
});
|
|
vi.stubGlobal('document', {
|
|
body: { dataset: { wppLocale: 'da' } },
|
|
querySelector: vi.fn(() => null),
|
|
});
|
|
vi.stubGlobal('navigator', { language: 'en-US' });
|
|
|
|
const i18n = await import('./lobby-i18n');
|
|
|
|
expect(i18n.resolvePreferredLocale()).toBe('da');
|
|
});
|
|
|
|
it('falls back to default en translation when da key is intentionally missing', async () => {
|
|
vi.stubGlobal('window', {
|
|
location: { search: '' },
|
|
localStorage: storageMock({ 'wpp.locale': 'da' }),
|
|
});
|
|
vi.stubGlobal('navigator', { language: 'da-DK' });
|
|
|
|
const i18n = await import('./lobby-i18n');
|
|
const catalogModule = await import('../../../../shared/i18n/lobby.json');
|
|
const catalog = catalogModule.default as {
|
|
frontend: {
|
|
ui: {
|
|
common: {
|
|
refresh: {
|
|
en?: string;
|
|
da?: string;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
const originalDa = catalog.frontend.ui.common.refresh.da;
|
|
catalog.frontend.ui.common.refresh.da = undefined;
|
|
|
|
try {
|
|
expect(i18n.t('common.refresh', 'da')).toBe(catalog.frontend.ui.common.refresh.en);
|
|
} finally {
|
|
catalog.frontend.ui.common.refresh.da = originalDa;
|
|
}
|
|
});
|
|
|
|
it('resolves baseline shell/game keys from shared namespaces', async () => {
|
|
vi.stubGlobal('window', {
|
|
location: { search: '' },
|
|
localStorage: storageMock({ 'wpp.locale': 'da' }),
|
|
});
|
|
vi.stubGlobal('navigator', { language: 'da-DK' });
|
|
|
|
const i18n = await import('./lobby-i18n');
|
|
|
|
const baselineKeys = [
|
|
'lobby.shell.title',
|
|
'lobby.shell.host_nav',
|
|
'lobby.shell.player_nav',
|
|
'lobby.shell.language_label',
|
|
'common.refresh',
|
|
'common.session_code',
|
|
'game.host.title',
|
|
'game.host.start_round',
|
|
'game.player.title',
|
|
'game.player.submit_guess',
|
|
] as const;
|
|
|
|
for (const key of baselineKeys) {
|
|
const value = i18n.t(key, 'da');
|
|
expect(value).toBeTypeOf('string');
|
|
expect(value.length).toBeGreaterThan(0);
|
|
expect(value).not.toBe(key);
|
|
}
|
|
});
|
|
|
|
it('exposes primary-only audio routing policy to clients', async () => {
|
|
vi.stubGlobal('window', {
|
|
location: { search: '' },
|
|
localStorage: storageMock({ 'wpp.locale': 'en' }),
|
|
});
|
|
vi.stubGlobal('navigator', { language: 'en-US' });
|
|
|
|
const i18n = await import('./lobby-i18n');
|
|
|
|
expect(i18n.clientHasNoAudioOutput).toBe(true);
|
|
});
|
|
});
|