waybetter/packages/shell/tests/models.test.ts
CRT b2a91ff7b3 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

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/);
});