import assert from 'node:assert/strict'; import test from 'node:test'; import { MACROS, buildItems, fuzzyScore, moveSelection, type PaletteContext } from '../src/shell/paletteModel.ts'; const context: PaletteContext = { tabs: [{ id: 't1', title: 'Home / X', url: 'https://x.com/home' }, { id: 't2', title: 'GitHub', url: 'https://github.com/' }], profiles: [{ id: 'default', name: 'Main' }, { id: 'pwork', name: 'Work' }], activeProfile: 'default', }; test('empty query lists every group without a search item', () => { const items = buildItems(context, ''); const groups = new Set(items.map(item => item.group)); for (const group of ['Tabs', 'Profiles', 'wayBetter for X', 'X', 'Account pages']) assert.ok(groups.has(group as never), group); assert.ok(!groups.has('Search')); }); test('fuzzy match ranks prefix above subsequence and rejects non-matches', () => { assert.ok(fuzzyScore('Bookmarks', 'book') > fuzzyScore('Blocked accounts', 'bok')); assert.equal(fuzzyScore('Home', 'xyz'), 0); assert.ok(fuzzyScore('Change username', 'chu') > 0); }); test('query ranks best match first and always ends with an X search', () => { const items = buildItems(context, 'github'); assert.equal(items[0]!.action.type, 'focus-tab'); const last = items.at(-1)!; assert.equal(last.group, 'Search'); assert.deepEqual(last.action, { type: 'open', url: 'https://x.com/search?q=github' }); }); test('search query is URL-encoded', () => { const search = buildItems(context, 'a&b c').at(-1)!; assert.deepEqual(search.action, { type: 'open', url: 'https://x.com/search?q=a%26b%20c' }); }); test('macros are deep links to exact https://x.com settings pages only', () => { const macros = buildItems(context, '').filter(item => item.group === 'Account pages'); assert.equal(macros.length, MACROS.length); for (const item of macros) { assert.equal(item.action.type, 'open'); const url = new URL((item.action as { url: string }).url); assert.equal(url.origin, 'https://x.com'); assert.ok(url.pathname.startsWith('/settings/')); } const deactivate = buildItems(context, 'deactivate')[0]!; assert.deepEqual(deactivate.action, { type: 'open', url: 'https://x.com/settings/deactivate' }); assert.deepEqual(buildItems(context, 'change username')[0]!.action, { type: 'open', url: 'https://x.com/settings/screen_name' }); }); test('every action type is navigation only (or the local Media panel, or the app menu)', () => { // `menu` opens the shell's own app menu (the titlebar's "Settings and more": language, theme, mode, zoom, the panel's // sections). It is local UI like the Media panel: the entry itself performs nothing on X and carries no URL or text. // `find` opens the shell's find bar for the tab in front: also local UI. What is typed there goes to the engine's // own find (shell-only `tab_find`), is kept nowhere and changes nothing on X; the entry itself carries no text. for (const item of buildItems(context, 'x')) assert.ok(['focus-tab', 'new-tab', 'panel', 'media', 'open', 'menu', 'find'].includes(item.action.type), item.action.type); assert.deepEqual(buildItems(context, '').find(item => item.id === 'find')?.action, { type: 'find' }, 'the find entry carries nothing but its type'); assert.deepEqual(buildItems(context, '').find(item => item.id === 'app-menu')?.action, { type: 'menu' }, 'the menu entry carries nothing but its type'); }); test('profiles and sections resolve to their actions', () => { assert.deepEqual(buildItems(context, 'work')[0]!.action, { type: 'new-tab', profile: 'pwork' }); assert.deepEqual(buildItems(context, 'themes')[0]!.action, { type: 'panel', section: 'themes' }); }); test('selection wraps', () => { assert.equal(moveSelection(0, -1, 3), 2); assert.equal(moveSelection(2, 1, 3), 0); assert.equal(moveSelection(0, 1, 0), 0); });