import { afterEach, describe, expect, it, vi } from 'vitest'; import { hostRouteContextResolver, playerRouteContextResolver, resolveSessionCode } from './session-route-context'; type RouteLike = { paramMap: { get: (key: string) => string | null }; queryParamMap: { get: (key: string) => string | null }; }; function route(params: Record, query: Record = {}): RouteLike { return { paramMap: { get: (key: string) => params[key] ?? null }, queryParamMap: { get: (key: string) => query[key] ?? null }, }; } function storageMock(initial: Record = {}): Storage { const data = new Map(Object.entries(initial)); return { getItem: vi.fn((key: string) => data.get(key) ?? null), setItem: vi.fn((key: string, value: string) => { data.set(key, value); }), removeItem: vi.fn((key: string) => { data.delete(key); }), clear: vi.fn(() => { data.clear(); }), key: vi.fn((index: number) => Array.from(data.keys())[index] ?? null), get length() { return data.size; }, } as unknown as Storage; } function setWindow(localStorage: Storage, sessionStorage: Storage): void { vi.stubGlobal('window', { localStorage, sessionStorage }); } describe('session route context', () => { afterEach(() => { vi.restoreAllMocks(); }); it('resolves player code from persisted session context when route has no code', () => { setWindow( storageMock({ 'wpp.session-context': JSON.stringify({ sessionCode: 'ab12', playerId: 7, token: 'tok' }) }), storageMock() ); expect(resolveSessionCode(route({}, {}) as never, 'player')).toBe('AB12'); }); it('resolves host code from session query string', () => { setWindow(storageMock(), storageMock()); expect(resolveSessionCode(route({}, { session: 'qwe9' }) as never, 'host')).toBe('QWE9'); }); it('player resolver emits player id/token when context matches route session', () => { setWindow( storageMock({ 'wpp.session-context': JSON.stringify({ sessionCode: 'AB12', playerId: 5, token: 'tok-5' }) }), storageMock() ); expect(playerRouteContextResolver(route({ context: 'ab12' }) as never, {} as never)).toEqual({ sessionCode: 'AB12', playerId: 5, token: 'tok-5', locale: 'en', }); }); it('host resolver stores normalized host session code for refresh bootstrap', () => { const sessionStorage = storageMock(); setWindow(storageMock(), sessionStorage); expect(hostRouteContextResolver(route({ context: 'ab12' }) as never, {} as never)).toEqual({ sessionCode: 'AB12', playerId: null, token: null, locale: 'en', }); expect(sessionStorage.setItem).toHaveBeenCalledWith('wpp.host-session-code', 'AB12'); }); it('resolvers normalize and expose locale from lang query param', () => { setWindow(storageMock(), storageMock()); expect(hostRouteContextResolver(route({}, { lang: 'da-DK' }) as never, {} as never).locale).toBe('da'); expect(playerRouteContextResolver(route({}, { lang: 'EN' }) as never, {} as never).locale).toBe('en'); }); it('does not reset persisted preferred locale when lang query param is absent', () => { const localStorage = storageMock({ 'wpp.locale': 'da' }); setWindow(localStorage, storageMock()); expect(hostRouteContextResolver(route({}, { lang: 'da' }) as never, {} as never).locale).toBe('da'); expect(hostRouteContextResolver(route({}, {}) as never, {} as never).locale).toBe('da'); expect(localStorage.setItem).toHaveBeenCalledTimes(1); expect(localStorage.setItem).toHaveBeenCalledWith('wpp.locale', 'da'); }); });