waybetter/packages/runtime/tests/hubs.test.ts
CRT 43a9e2e1a7 Extract the wayBetter framework core from wayBetter for X
Site-agnostic parts of the X app, behind a small site adapter:

- crates/waybetter-host: webview-label security policy and tab rect
  clamp, account profiles with native-owned accounts (AccountRules),
  plugin store with bundled-plugin upgrades (PluginHost), signed
  data-only hotfix channel, update scheduling and crash handshake, tab
  payloads/link routing/tab-chrome validation, and (feature "tauri")
  webview helpers and the Windows system menu.
- packages/runtime: DOM hub, request hub, plugin API core, OKLab theme
  color model, split-view rail hiding, link routing and account
  reporting, each configured by a SiteAdapter.
- packages/shell: address bar, pane, link-drop, profile, palette and
  update-chip models, and window controls with the Tauri API injected.

Tests moved/adapted from the app: 45 Rust (headless), 40 TypeScript.
2026-09-28 03:01:33 +07:00

212 lines
9.8 KiB
TypeScript

import assert from 'node:assert/strict';
import { test } from 'node:test';
import { configureRuntime, isSiteUrl, type SiteAdapter } from '../src/site.ts';
import { addGate, addRewrite, addTap, addXhrGate, fetchHooked, isApiUrl } from '../src/netHub.ts';
import { isHeavyPath, watchDom } from '../src/domHub.ts';
const SITE: SiteAdapter = {
product: 'wayBetter for Test',
hosts: ['site.test'],
runtimeGlobal: 'Demo',
heavyPath: /^\/chat(?:\/|$)/,
apiMarks: ['/api/'],
xhrMarks: ['/upload'],
plugins: { name: 'Demo', product: 'wayBetter for Test', storagePrefix: 'demo-plugin:', styleDataKey: 'demoPlugin', toastId: 'demo-toast', toastBackground: '#000', itemSelector: 'article', chordKeys: [] },
};
configureRuntime(SITE);
const tick = () => new Promise(resolve => setTimeout(resolve, 15));
const json = (body: unknown) => new Response(JSON.stringify(body), { status: 200, headers: { 'content-type': 'application/json' } });
function countClones() {
const original = Response.prototype.clone;
let clones = 0;
Response.prototype.clone = function (this: Response) { clones++; return original.call(this); };
return { get count() { return clones; }, restore() { Response.prototype.clone = original; } };
}
test('site adapter: origin and API marks come from the adapter', () => {
assert.ok(isSiteUrl(SITE, 'https://site.test/a'));
for (const url of ['http://site.test/', 'https://site.test.evil/', 'https://u:p@site.test/', 'nope']) assert.equal(isSiteUrl(SITE, url), false, url);
assert.ok(isApiUrl('https://site.test/api/v1/feed'));
assert.equal(isApiUrl('https://cdn.site.test/a.png'), false);
assert.ok(isHeavyPath('/chat/1') && !isHeavyPath('/home'));
});
test('net hub: non-API requests skip every stage and get the inner promise itself', async () => {
const promise = Promise.resolve(json({}));
const target = { fetch: (() => promise) as unknown as typeof fetch };
const seen: string[] = [];
const offTap = addTap(target, { match: url => { seen.push(url); return true; }, maxBytes: 1e6, onJson() {} });
const offRewrite = addRewrite(target, () => { seen.push('rewrite'); return null; });
for (const url of ['https://cdn.site.test/app.wasm', 'https://cdn.site.test/a.jpg', '/sw.js']) {
assert.equal(target.fetch(url), promise, url);
}
assert.deepEqual(seen, [], 'no URL parsing, matching or cloning for non-API traffic');
offTap();
offRewrite();
});
test('net hub: one clone and one parse feed every matching tap', async () => {
const clones = countClones();
try {
const body = { data: { ok: true } };
const target = { fetch: (async () => json(body)) as typeof fetch };
const got: unknown[] = [];
const a = addTap(target, { match: url => url.includes('Feed'), maxBytes: 1e6, onJson: value => got.push(value) });
const b = addTap(target, { match: url => url.includes('HomeFeed'), maxBytes: 1e6, onJson: value => got.push(value) });
const c = addTap(target, { match: () => false, maxBytes: 1e6, onJson: value => got.push(value) });
const response = await target.fetch('https://site.test/api/HomeFeed?cursor=1');
assert.deepEqual(await response.json(), body, 'the page still reads the original body');
await tick();
assert.equal(clones.count, 1);
assert.equal(got.length, 2);
assert.equal(got[0], got[1], 'both taps get the same parsed object');
a(); b(); c();
} finally {
clones.restore();
}
});
test('net hub: oversized bodies are never cloned', async () => {
const clones = countClones();
try {
const response = new Response('{}', { status: 200, headers: { 'content-length': '5000000' } });
const target = { fetch: (async () => response) as typeof fetch };
const off = addTap(target, { match: () => true, maxBytes: 1000, onJson() { assert.fail('not delivered'); } });
await target.fetch('/api/big');
await tick();
assert.equal(clones.count, 0);
off();
} finally {
clones.restore();
}
});
test('net hub: rewrite runs before the gate; unregistering the last stage restores fetch', async () => {
const calls: unknown[] = [];
const inner = ((input: unknown) => { calls.push(input); return Promise.resolve(json({})); }) as typeof fetch;
const target = { fetch: inner };
const order: string[] = [];
const offRewrite = addRewrite(target, input => { order.push('rewrite'); return String(input).replace('A', 'B'); });
const offGate = addGate(target, (input, _init, send) => { order.push(`gate:${String(input)}`); return String(input).endsWith('B') ? send() : null; });
assert.ok(fetchHooked(target));
await target.fetch('/api/A');
assert.deepEqual(order, ['rewrite', 'gate:/api/B']);
assert.deepEqual(calls, ['/api/B']);
offRewrite();
assert.notEqual(target.fetch, inner, 'still hooked while the gate is registered');
offGate();
assert.equal(target.fetch, inner);
assert.equal(fetchHooked(target), false);
});
test('net hub: XHR gates see API and XHR-mark requests only, and can rewrite the URL', () => {
const sent: unknown[] = [];
class FakeXhr {
url = '';
open(_method: string, url: string) { this.url = url; }
send(body?: unknown) { sent.push([this.url, body]); }
addEventListener() {}
}
const target = { fetch: (async () => json({})) as typeof fetch, XMLHttpRequest: FakeXhr as unknown as typeof XMLHttpRequest };
const seen: string[] = [];
const off = addXhrGate(target, {
open(_xhr, request) { return request.url.includes('/api/old') ? request.url.replace('old', 'new') : undefined; },
send(_xhr, request, body, sendNative) { seen.push(request.url); sendNative(`${String(body)}!`); return true; },
});
for (const url of ['/api/old', '/upload/chunk', '/static/app.js']) {
const xhr = new FakeXhr();
xhr.open('post', url);
xhr.send('body');
}
assert.deepEqual(seen, ['/api/new', '/upload/chunk']);
assert.deepEqual(sent, [['/api/new', 'body!'], ['/upload/chunk', 'body!'], ['/static/app.js', 'body']]);
off();
assert.equal(FakeXhr.prototype.send.name, 'send', 'the last gate restores the prototype');
});
function fakeDomWindow(pathname = '/home') {
const frames: FrameRequestCallback[] = [];
const observers: { callback: MutationCallback; options?: MutationObserverInit; connected: boolean }[] = [];
class FakeObserver {
entry: { callback: MutationCallback; options?: MutationObserverInit; connected: boolean };
constructor(callback: MutationCallback) { this.entry = { callback, connected: false }; observers.push(this.entry); }
observe(_node: Node, options: MutationObserverInit) { this.entry.options = options; this.entry.connected = true; }
disconnect() { this.entry.connected = false; }
}
const win = {
document: { body: {}, addEventListener() {}, removeEventListener() {} } as unknown as Document,
location: { pathname },
MutationObserver: FakeObserver as unknown as typeof MutationObserver,
requestAnimationFrame(callback: FrameRequestCallback) { frames.push(callback); return frames.length; },
};
const flush = () => { for (const frame of frames.splice(0)) frame(0); };
const mutate = (...types: MutationRecordType[]) => observers[0]!.callback(types.map(type => ({ type, attributeName: type === 'attributes' ? 'aria-selected' : null })) as unknown as MutationRecord[], {} as MutationObserver);
return { win, frames, observers, flush, mutate };
}
test('dom hub: one observer and one frame for all features; kinds and phases respected', () => {
const { win, frames, observers, flush, mutate } = fakeDomWindow();
const runs: string[] = [];
const writer = watchDom(win, () => runs.push('writer'));
const text = watchDom(win, () => runs.push('text'), { characterData: true });
const reader = watchDom(win, () => runs.push('reader'), { phase: 'read' });
const tabs = watchDom(win, () => runs.push('tabs'), { attributes: ['aria-selected'] });
assert.equal(observers.length, 1);
assert.deepEqual(observers[0]!.options, { childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ['aria-selected'] });
mutate('childList'); mutate('childList'); mutate('characterData');
assert.equal(frames.length, 1, 'many mutation callbacks, one frame');
flush();
assert.deepEqual(runs, ['reader', 'writer', 'text', 'tabs'], 'layout readers before DOM writers');
runs.length = 0;
mutate('characterData');
flush();
assert.deepEqual(runs, ['text'], 'text changes only wake features that asked for them');
runs.length = 0;
mutate('attributes');
flush();
assert.deepEqual(runs, ['tabs']);
runs.length = 0;
writer.schedule(); writer.schedule();
flush();
assert.deepEqual(runs, ['writer'], 'explicit schedule runs once per frame');
for (const handle of [writer, text, reader, tabs]) handle.dispose();
assert.equal(observers[0]!.connected, false, 'last feature off disconnects the observer');
});
test('dom hub: heavy pages skip features marked offOnHeavy', () => {
const { win, flush, mutate } = fakeDomWindow('/chat/123');
const runs: string[] = [];
const media = watchDom(win, () => runs.push('media'), { offOnHeavy: true });
const nav = watchDom(win, () => runs.push('nav'));
mutate('childList');
flush();
assert.deepEqual(runs, ['nav']);
win.location.pathname = '/home';
mutate('childList');
flush();
assert.deepEqual(runs, ['nav', 'media', 'nav']);
media.dispose();
nav.dispose();
});
test('dom hub: a failing feature does not stop the others', () => {
const { win, flush, mutate } = fakeDomWindow();
const runs: string[] = [];
const errors: unknown[] = [];
const original = console.error;
console.error = (...args: unknown[]) => { errors.push(args[0]); };
try {
const bad = watchDom(win, () => { throw new Error('boom'); });
const good = watchDom(win, () => runs.push('good'));
mutate('childList');
flush();
assert.deepEqual(runs, ['good']);
assert.deepEqual(errors, ['[wayBetter for Test] DOM feature failed']);
bad.dispose();
good.dispose();
} finally {
console.error = original;
}
});