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