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. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
212 lines
9.8 KiB
TypeScript
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;
|
|
}
|
|
});
|