diablo2-web/tests/lang.test.ts

106 lines
3.7 KiB
TypeScript

import { afterEach, describe, expect, it } from 'vitest'
import {
CHINESE_ENTRY_PAGES,
DEFAULT_GAME_LANG,
DEFAULT_TBL_LANG,
ENGLISH_ENTRY_PAGES,
detectInitialLang,
detectLangFromUrl,
getGameLang,
getTblLang,
onGameLangChange,
onLangChange,
onTblLangChange,
resolveLangFromPathname,
setGameLang,
setLang,
setTblLang,
} from '../src/client/i18n/lang.ts'
describe('i18n lang.ts — URL path resolution & fail-fast listener error propagation (Issue #712)', () => {
afterEach(() => {
setGameLang(null)
})
it('exports canonical entry page lists and resolves pathname deterministically', () => {
expect(DEFAULT_GAME_LANG).toBe('zh')
expect(DEFAULT_TBL_LANG).toBe('CHI')
expect(ENGLISH_ENTRY_PAGES).toContain('acts-en.html')
expect(ENGLISH_ENTRY_PAGES).toContain('bnet.html')
expect(CHINESE_ENTRY_PAGES).toContain('acts-zh.html')
expect(CHINESE_ENTRY_PAGES).toContain('acts.html')
expect(resolveLangFromPathname('/acts-en.html')).toBe('en')
expect(resolveLangFromPathname('/arena-en.html')).toBe('en')
expect(resolveLangFromPathname('/bnet.html')).toBe('en')
expect(resolveLangFromPathname('/play-en.html')).toBe('en')
expect(resolveLangFromPathname('/acts-zh.html')).toBe('zh')
expect(resolveLangFromPathname('/arena-zh.html')).toBe('zh')
expect(resolveLangFromPathname('/acts.html')).toBe('zh')
expect(resolveLangFromPathname('/arena.html')).toBe('zh')
expect(resolveLangFromPathname('/index.html')).toBe('zh')
expect(resolveLangFromPathname('/play.html')).toBeNull()
expect(resolveLangFromPathname('/unknown-route')).toBeNull()
})
it('resolves detectLangFromUrl across query params, entry pages, and backward-compatible default', () => {
expect(detectLangFromUrl('http://localhost/acts-en.html')).toBe('ENG')
expect(detectLangFromUrl('http://localhost/acts-zh.html')).toBe('CHI')
expect(detectLangFromUrl('http://localhost/acts.html')).toBe('CHI')
expect(detectLangFromUrl('http://localhost/acts.html?lang=en')).toBe('ENG')
expect(detectLangFromUrl('http://localhost/acts-en.html?lang=zh')).toBe('CHI')
expect(detectLangFromUrl('http://localhost/unrecognized.html')).toBe('CHI')
expect(detectInitialLang()).toBe('zh')
})
it('propagates a single listener error fail-fast instead of silently swallowing it', () => {
const unsub = onLangChange((lang) => {
throw new Error(`Font atlas reload failed for ${lang}`)
})
try {
expect(() => setLang('en')).toThrow('Font atlas reload failed for en')
expect(getGameLang()).toBe('en')
expect(getTblLang()).toBe('ENG')
} finally {
unsub()
}
})
it('notifies all remaining listeners and throws AggregateError when multiple listeners fail', () => {
const seen: string[] = []
const unsub1 = onTblLangChange((tbl) => {
seen.push(`tbl:${tbl}`)
throw new Error('First tbl listener failure')
})
const unsub2 = onGameLangChange((lang) => {
seen.push(`game:${lang}`)
throw new Error('Second gameLang listener failure')
})
const unsub3 = onGameLangChange((lang) => {
seen.push(`ok:${lang}`)
})
try {
let caught: unknown = null
try {
setTblLang('ENG')
} catch (err) {
caught = err
}
expect(caught).toBeInstanceOf(AggregateError)
const agg = caught as AggregateError
expect(agg.errors).toHaveLength(2)
expect((agg.errors[0] as Error).message).toBe('First tbl listener failure')
expect((agg.errors[1] as Error).message).toBe('Second gameLang listener failure')
// All 3 listeners must have been invoked despite earlier listener throws
expect(seen).toEqual(['tbl:ENG', 'game:en', 'ok:en'])
} finally {
unsub1()
unsub2()
unsub3()
}
})
})