import assert from 'node:assert/strict'; import test from 'node:test'; import { applySuggestion, buildSearchUrl, describeQuery, hasOperators, normalizeQuery, suggest, tokenAt } from '../src/core/searchOperators.ts'; import { toUrl } from '../src/shell/searchInput.ts'; import { buildItems } from '../src/shell/paletteModel.ts'; test('operator detection', () => { for (const q of ['from:@jack hello', 'to:x', '@nasa', 'since:2024-01-01', 'cats filter:media', 'dogs -filter:replies', 'min_faves:100', 'lang:tr', 'list:123', 'url:github.com', '"exact phrase"', 'a OR b']) assert.ok(hasOperators(q), q); for (const q of ['hello world', 'from:', 'email me@', 'or maybe']) assert.ok(!hasOperators(q), q); }); test('from:@x is normalized and opens Latest with src=typed_query', () => { assert.equal(normalizeQuery(' from:@jack hi -to:@x '), 'from:jack hi -to:x'); assert.equal(buildSearchUrl('from:@jack hello'), 'https://x.com/search?q=from%3Ajack%20hello&src=typed_query&f=live'); assert.equal(buildSearchUrl('plain words'), 'https://x.com/search?q=plain%20words&src=typed_query'); }); test('address bar input: operators search X, URLs and domains navigate', () => { assert.equal(toUrl('from:@x cats'), 'https://x.com/search?q=from%3Ax%20cats&src=typed_query&f=live'); assert.equal(toUrl('url:github.com'), 'https://x.com/search?q=url%3Agithub.com&src=typed_query&f=live'); assert.equal(toUrl('github.com'), 'https://github.com'); assert.equal(toUrl('https://x.com/home'), 'https://x.com/home'); assert.equal(toUrl('just words'), 'https://x.com/search?q=just%20words'); }); test('palette: an operator query searches Latest and offers completions for the token', () => { const context = { tabs: [], profiles: [], activeProfile: 'default' }; const items = buildItems(context, 'from:@jack'); assert.deepEqual(items.at(-1)!.action, { type: 'open', url: 'https://x.com/search?q=from%3Ajack&src=typed_query&f=live' }); const partial = buildItems(context, 'cats fil'); const complete = partial.filter(item => item.action.type === 'complete'); assert.ok(complete.some(item => item.action.type === 'complete' && item.action.text === 'cats filter:media ')); }); test('tokenAt finds the caret token', () => { assert.deepEqual(tokenAt('cats fro dogs', 8), { start: 5, end: 8, text: 'fro' }); assert.deepEqual(tokenAt('cats ', 5), { start: 5, end: 5, text: '' }); }); test('suggest: partial operators, dates, languages and numbers', () => { assert.deepEqual(suggest('fil', 3).map(s => s.insert), ['filter:media', 'filter:images', 'filter:videos', 'filter:links', 'filter:replies']); assert.deepEqual(suggest('-fil', 4).map(s => s.insert), ['-filter:replies']); const now = new Date(2026, 8, 27); assert.deepEqual(suggest('since:', 6, now).map(s => s.insert).slice(0, 2), ['since:2026-09-27', 'since:2026-09-20']); assert.deepEqual(suggest('lang:t', 6).map(s => s.insert), ['lang:tr']); assert.deepEqual(suggest('min_faves:1', 11).map(s => s.insert), ['min_faves:10', 'min_faves:100', 'min_faves:1000', 'min_faves:10000']); assert.ok(suggest('', 0).length > 0); assert.deepEqual(suggest('from:jack', 9), []); }); test('applySuggestion replaces the token and places the caret', () => { assert.deepEqual(applySuggestion('cats fil', 8, 'filter:media'), { text: 'cats filter:media ', caret: 18 }); assert.deepEqual(applySuggestion('cats fr', 7, 'from:'), { text: 'cats from:', caret: 10 }); assert.deepEqual(applySuggestion('cats ', 5, '""'), { text: 'cats ""', caret: 6 }); assert.deepEqual(applySuggestion('cats ', 5, 'OR'), { text: 'cats OR ', caret: 8 }); }); test('describeQuery marks operators', () => { assert.deepEqual(describeQuery('from:@a cats -dogs "big cat"'), [ { text: 'from:a', operator: true }, { text: 'cats', operator: false }, { text: '-dogs', operator: true }, { text: '"big cat"', operator: true }, ]); });