waybetter/packages/runtime/tests/pageLang.test.ts
CRT b2744c1a45 pageLang: report X's language even when installed before <html> exists or before the IPC bridge
At document start (where the app installs the X runtime) there is no <html> element yet, so
the lang report never ran and "Follow X" never learned X's language (found live: siteLang
stayed null). It now reads lang again at readystatechange/DOMContentLoaded, and a report
that fails (no bridge yet) is retried with a bounded backoff. Tests.
2026-09-29 11:01:53 +07:00

75 lines
3 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { installPageLangReport, LANG_REPORT_RETRIES } from '../src/pageLang.ts';
/** A page whose <html lang> never changes, with timers we step by hand. */
function fakePage(lang: string, parsed = true) {
const timers: Array<() => void> = [];
const listeners = new Map<string, () => void>();
const root = { getAttribute: (name: string) => (name === 'lang' ? lang : null) };
const document = {
documentElement: parsed ? root : null,
addEventListener: (type: string, fn: () => void) => { listeners.set(type, fn); },
removeEventListener: (type: string) => { listeners.delete(type); },
};
const win = {
document,
MutationObserver: class { observe() {} disconnect() {} },
setTimeout: (fn: () => void) => { timers.push(fn); return timers.length; },
clearTimeout: () => {},
} as unknown as Window & typeof globalThis;
const parse = () => { document.documentElement = root; listeners.get('DOMContentLoaded')?.(); };
return { win, timers, parse, listeners };
}
const settle = () => new Promise(resolve => setImmediate(resolve));
test('a language report lost at document start (no IPC bridge yet) is sent again', async () => {
const { win, timers } = fakePage('en-GB');
const sent: unknown[] = [];
let bridge = false;
installPageLangReport(win, async (command, args) => {
if (!bridge) throw new Error('IPC bridge not ready');
sent.push([command, args]);
});
await settle();
assert.equal(sent.length, 0);
assert.equal(timers.length, 1, 'a retry is scheduled');
bridge = true;
timers.shift()!();
await settle();
assert.deepEqual(sent, [['tab_report', { report: { lang: 'en-GB' } }]]);
assert.equal(timers.length, 0, 'nothing more once it went through');
});
test('retries are bounded and stop after dispose', async () => {
const { win, timers } = fakePage('tr');
let calls = 0;
const handle = installPageLangReport(win, async () => { calls += 1; throw new Error('down'); });
for (let i = 0; i < LANG_REPORT_RETRIES + 5; i += 1) { await settle(); timers.shift()?.(); }
await settle();
assert.equal(calls, LANG_REPORT_RETRIES + 1);
assert.equal(timers.length, 0);
const second = fakePage('ja');
let later = 0;
const h2 = installPageLangReport(second.win, async () => { later += 1; throw new Error('down'); });
await settle();
h2.dispose();
second.timers.shift()?.();
await settle();
assert.equal(later, 1, 'no report after dispose');
handle.dispose();
});
test('installed at document start before <html> exists, it reports once the parser made it', async () => {
const page = fakePage('en-GB', false);
const sent: unknown[] = [];
const handle = installPageLangReport(page.win, async (command, args) => { sent.push([command, args]); });
await settle();
assert.equal(sent.length, 0);
page.parse();
await settle();
assert.deepEqual(sent, [['tab_report', { report: { lang: 'en-GB' } }]]);
handle.dispose();
assert.equal(page.listeners.size, 0);
});