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.
112 lines
6.8 KiB
TypeScript
112 lines
6.8 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { parseAddress, type AddressSite } from '../src/addressModel.ts';
|
|
import { besideGroup, redundantNavPanes, withNavRedundant, type PaneBox } from '../src/paneModel.ts';
|
|
import { fuzzyScore, moveSelection, rankItems } from '../src/palette.ts';
|
|
import { accountLabel, addProfile, normalizeProfiles, removeProfile, tabProfile, type AccountRules } from '../src/profileModel.ts';
|
|
import { chipFor, parseUpdateStatus } from '../src/updateModel.ts';
|
|
|
|
const SITE: AddressSite<'kick'> = {
|
|
kind: 'kick',
|
|
hosts: new Set(['kick.com', 'www.kick.com']),
|
|
label: 'Kick',
|
|
home: origin => `${origin}/`,
|
|
segments: url => {
|
|
const [channel] = url.pathname.split('/').filter(Boolean);
|
|
if (channel === 'settings') return null;
|
|
return channel ? [{ label: channel, href: `${url.origin}/${channel}` }] : [{ label: 'Home', href: `${url.origin}/` }];
|
|
},
|
|
};
|
|
|
|
test('address: the site adapter names its own pages; other sites get host chips', () => {
|
|
const kick = parseAddress('https://kick.com/somechannel', SITE)!;
|
|
assert.deepEqual(kick.site, { kind: 'kick', label: 'Kick', href: 'https://kick.com/' });
|
|
assert.deepEqual(kick.segments.map(s => s.label), ['somechannel']);
|
|
assert.equal((kick.segments[0] as { current?: boolean }).current, true);
|
|
assert.equal(parseAddress('https://kick.com/settings/profile', SITE), null, 'an internal page shows no chips');
|
|
const other = parseAddress('http://www.example.com/a/b%20c?q=1', SITE)!;
|
|
assert.deepEqual(other.site, { kind: 'host', label: 'example.com', href: 'http://www.example.com' });
|
|
assert.deepEqual(other.segments.map(s => s.label), ['a', 'b c', '?…']);
|
|
assert.equal(other.insecure, true);
|
|
for (const bad of ['about:blank', 'file:///c:/x', 'javascript:1', 'nonsense']) assert.equal(parseAddress(bad, SITE), null, bad);
|
|
assert.equal(parseAddress('https://kick.com/x')!.site.kind, 'host', 'without an adapter every site is a host');
|
|
});
|
|
|
|
const onSite = (url: string) => url.startsWith('https://site.test/');
|
|
const pane = (id: string, x: number, y = 0, extra: Partial<PaneBox> = {}): PaneBox =>
|
|
({ id, url: 'https://site.test/feed', profile: 'default', x, y, visible: true, ...extra });
|
|
|
|
test('panes: only the leftmost (then topmost) site pane of an account keeps the rail', () => {
|
|
assert.deepEqual([...redundantNavPanes([pane('right', 800), pane('left', 0)], onSite)], ['right']);
|
|
assert.deepEqual([...redundantNavPanes([pane('bottom', 0, 400), pane('top', 0, 40)], onSite)], ['bottom']);
|
|
assert.equal(redundantNavPanes([pane('other', 0, 0, { url: 'https://example.com/' }), pane('site', 700)], onSite).size, 0);
|
|
assert.deepEqual([...redundantNavPanes([pane('main', 0), pane('alt', 700, 0, { profile: 'p2' }), pane('main-2', 1400)], onSite)], ['main-2']);
|
|
});
|
|
|
|
test('panes: hidden tabs keep their last navRedundant value; closed tabs are forgotten', () => {
|
|
const memory = new Map<string, boolean>();
|
|
const first = withNavRedundant([pane('a', 0), pane('b', 700)], onSite, memory);
|
|
assert.deepEqual(first.map(item => item.navRedundant), [false, true]);
|
|
const hidden = withNavRedundant([pane('a', 0, 0, { visible: false }), pane('b', 700, 0, { visible: false })], onSite, memory);
|
|
assert.deepEqual(hidden.map(item => item.navRedundant), [false, true], 'an overlay changes nothing');
|
|
withNavRedundant([pane('a', 0)], onSite, memory);
|
|
assert.deepEqual([...memory.keys()], ['a']);
|
|
});
|
|
|
|
test('panes: beside links reuse the side pane this source opened', () => {
|
|
const source = { id: 'g1', left: 0, top: 0, right: 600, bottom: 800 };
|
|
const side = { id: 'g2', left: 600, top: 0, right: 1200, bottom: 800, besideOf: ['g1'] };
|
|
const mine = { id: 'g3', left: 600, top: 0, right: 1200, bottom: 800 };
|
|
assert.equal(besideGroup(source, [source, mine, side]), 'g2');
|
|
assert.equal(besideGroup(source, [source, mine]), null, 'a group the user opened is never taken over');
|
|
assert.equal(besideGroup(source, [{ ...side, left: 0, right: 300 }]), null, 'not to the right any more');
|
|
});
|
|
|
|
test('palette: prefix beats substring beats subsequence; ranking is stable', () => {
|
|
assert.ok(fuzzyScore('Notifications', 'noti') > fuzzyScore('Annotations', 'noti'));
|
|
assert.ok(fuzzyScore('Split right', 'spr') > 0);
|
|
assert.equal(fuzzyScore('Home', 'xyz'), 0);
|
|
const items = [
|
|
{ label: 'Open settings', group: 'App' },
|
|
{ label: 'Settings', group: 'App' },
|
|
{ label: 'Search', group: 'Search', keywords: 'settings' },
|
|
{ label: 'Other', group: 'App', keywords: 'settings page' },
|
|
];
|
|
assert.deepEqual(rankItems(items, 'settings', item => item.group === 'Search').map(i => i.label), ['Settings', 'Open settings', 'Other']);
|
|
assert.equal(moveSelection(0, -1, 3), 2);
|
|
assert.equal(moveSelection(2, 1, 3), 0);
|
|
assert.equal(moveSelection(5, 1, 0), 0);
|
|
});
|
|
|
|
const RULES: AccountRules = { handle: /^[a-z0-9_]{3,25}$/, avatarPrefixes: ['https://files.site.test/'], handlePrefix: '' };
|
|
|
|
test('profiles: storage input is normalized with the site account rules', () => {
|
|
const settings = normalizeProfiles({
|
|
active: 'p9',
|
|
profiles: [
|
|
{ id: 'p1', name: ' Work ', color: '#ABCDEF', account: { handle: 'streamer_1', name: '', signedIn: true, avatar: 'https://evil.test/a.png' } },
|
|
{ id: 'p1', name: 'Duplicate', color: '#000000' },
|
|
{ id: '../x', name: 'Bad', color: '#000000' },
|
|
{ id: 'p2', name: 'Bad handle', color: '#000000', account: { handle: 'no', name: 'x', signedIn: true } },
|
|
],
|
|
}, RULES);
|
|
assert.deepEqual(settings.profiles.map(p => p.id), ['default', 'p1', 'p2']);
|
|
assert.equal(settings.active, 'default');
|
|
assert.deepEqual(settings.profiles[1]!.account, { handle: 'streamer_1', name: 'streamer_1', signedIn: true });
|
|
assert.equal(settings.profiles[2]!.account, undefined);
|
|
assert.deepEqual(accountLabel(settings.profiles[1]!, RULES), { title: 'streamer_1', detail: 'streamer_1', state: 'signed-in' });
|
|
assert.equal(accountLabel({ ...settings.profiles[1]!, account: { handle: 'a_b', name: 'A', signedIn: false } }, { handlePrefix: '@' }).detail, '@a_b · signed out');
|
|
const added = addProfile(settings, '', 'p3');
|
|
assert.equal(added.active, 'p3');
|
|
assert.equal(removeProfile(added, 'default'), added, 'the default profile stays');
|
|
assert.equal(removeProfile(added, 'p3').active, 'default');
|
|
assert.equal(tabProfile(added, 'gone').id, 'default');
|
|
});
|
|
|
|
test('update chip: only actionable states show, with the product name', () => {
|
|
assert.deepEqual(parseUpdateStatus({ state: 'ready', version: '1.2.0' }), { state: 'ready', version: '1.2.0', notes: '' });
|
|
assert.deepEqual(parseUpdateStatus({ state: 'problem', downloadUrl: 'http://insecure' }), { state: 'idle' });
|
|
assert.deepEqual(parseUpdateStatus('nope'), { state: 'idle' });
|
|
assert.equal(chipFor({ state: 'checking' }, 'wayBetter for Kick'), null);
|
|
assert.match(chipFor({ state: 'ready', version: '1.2.0', notes: '' }, 'wayBetter for Kick')!.title, /wayBetter for Kick 1\.2\.0/);
|
|
});
|