2f867b8e6c
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 1m10s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m41s
An installed PWA (standalone web + confirmed email) warms the dictionaries for the player's enabled variants while online — on lobby entry and on a variant-preference change — using the per-variant version the profile now advertises, so a later switch to offline mode already has the data. - dict/preload.ts: pure preloadDicts (retry + linear backoff, honours the session dictionary miss-breaker); node-tested. - dict/preloadrun.ts: lazy browser orchestration (real getDawg), imported dynamically so the loader and move generator stay out of the main bundle. - offline.svelte.ts: kickDictPreload, gated by the pure, tested offlinePreloadEligible, plus the reactive first-lobby preload warning. - Header shows a poor-connection notice in the ad-banner slot on a first-lobby preload failure (offline.preloadWarning, en/ru). - App-entry bundle budget 112->113 for the irreducible main-side wiring (documented in bundle-size.mjs); the heavy parts remain lazy chunks. Docs: ARCHITECTURE offline-mode + dict-preload mechanism.
87 lines
2.8 KiB
TypeScript
87 lines
2.8 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { preloadDicts } from './preload';
|
|
import type { Variant } from '../model';
|
|
import type { Dawg } from './dawg';
|
|
|
|
// A non-null stand-in for a loaded reader — preloadDicts only checks getDawg's result for null.
|
|
const DAWG = {} as Dawg;
|
|
const noSleep = (): Promise<void> => Promise.resolve();
|
|
|
|
describe('preloadDicts', () => {
|
|
it('fetches every enabled variant that has a known version', async () => {
|
|
const calls: string[] = [];
|
|
const res = await preloadDicts({ scrabble_en: 'v1', scrabble_ru: 'v2', erudit_ru: 'v3' }, ['scrabble_en', 'erudit_ru'], {
|
|
getDawg: async (v: Variant, ver: string) => {
|
|
calls.push(`${v}@${ver}`);
|
|
return DAWG;
|
|
},
|
|
disabled: () => false,
|
|
sleep: noSleep,
|
|
});
|
|
expect(res.ok).toEqual(['scrabble_en', 'erudit_ru']);
|
|
expect(res.failed).toEqual([]);
|
|
expect(calls).toEqual(['scrabble_en@v1', 'erudit_ru@v3']);
|
|
});
|
|
|
|
it('marks a variant with no known version as failed without fetching it', async () => {
|
|
const calls: string[] = [];
|
|
const res = await preloadDicts({ scrabble_en: 'v1' }, ['scrabble_en', 'scrabble_ru'], {
|
|
getDawg: async (v: Variant) => {
|
|
calls.push(v);
|
|
return DAWG;
|
|
},
|
|
disabled: () => false,
|
|
sleep: noSleep,
|
|
});
|
|
expect(res.ok).toEqual(['scrabble_en']);
|
|
expect(res.failed).toEqual(['scrabble_ru']);
|
|
expect(calls).toEqual(['scrabble_en']);
|
|
});
|
|
|
|
it('retries a transient failure with linear backoff, then succeeds', async () => {
|
|
let attempts = 0;
|
|
const waits: number[] = [];
|
|
const res = await preloadDicts({ scrabble_en: 'v1' }, ['scrabble_en'], {
|
|
getDawg: async () => (++attempts >= 3 ? DAWG : null),
|
|
disabled: () => false,
|
|
sleep: async (ms: number) => void waits.push(ms),
|
|
retries: 3,
|
|
backoffMs: 100,
|
|
});
|
|
expect(res.ok).toEqual(['scrabble_en']);
|
|
expect(attempts).toBe(3);
|
|
expect(waits).toEqual([100, 200]);
|
|
});
|
|
|
|
it('gives up a persistent failure after the retry budget', async () => {
|
|
let attempts = 0;
|
|
const res = await preloadDicts({ scrabble_en: 'v1' }, ['scrabble_en'], {
|
|
getDawg: async () => {
|
|
attempts++;
|
|
return null;
|
|
},
|
|
disabled: () => false,
|
|
sleep: noSleep,
|
|
retries: 2,
|
|
});
|
|
expect(res.failed).toEqual(['scrabble_en']);
|
|
expect(res.ok).toEqual([]);
|
|
expect(attempts).toBe(3);
|
|
});
|
|
|
|
it('stops retrying once the session miss-breaker trips', async () => {
|
|
let attempts = 0;
|
|
const res = await preloadDicts({ scrabble_en: 'v1' }, ['scrabble_en'], {
|
|
getDawg: async () => {
|
|
attempts++;
|
|
return null;
|
|
},
|
|
disabled: () => true,
|
|
sleep: noSleep,
|
|
retries: 5,
|
|
});
|
|
expect(res.failed).toEqual(['scrabble_en']);
|
|
expect(attempts).toBe(1);
|
|
});
|
|
});
|