102 lines
3.7 KiB
TypeScript
102 lines
3.7 KiB
TypeScript
import { detectQueryType, parseSearchQuery, queryToPredicate } from './search-parser';
|
|
import { SearchContext } from './search-parser.types';
|
|
|
|
type TestFn = () => void;
|
|
|
|
interface TestResult {
|
|
name: string;
|
|
error?: Error;
|
|
}
|
|
|
|
const testResults: TestResult[] = [];
|
|
|
|
const runTest = (name: string, fn: TestFn): void => {
|
|
try {
|
|
fn();
|
|
testResults.push({ name });
|
|
} catch (error) {
|
|
testResults.push({ name, error: error instanceof Error ? error : new Error(String(error)) });
|
|
console.error(`[search-parser spec] ${name} failed`, error);
|
|
}
|
|
};
|
|
|
|
const assert = (condition: boolean, message: string): void => {
|
|
if (!condition) {
|
|
throw new Error(message);
|
|
}
|
|
};
|
|
|
|
const assertEqual = <T>(actual: T, expected: T, message: string): void => {
|
|
assert(actual === expected, `${message} (expected ${expected}, received ${actual})`);
|
|
};
|
|
|
|
const createContext = (overrides: Partial<SearchContext> = {}): SearchContext => ({
|
|
filePath: 'notes/example.md',
|
|
fileName: 'example',
|
|
fileNameWithExt: 'example.md',
|
|
content: 'Hello world with #tag in section heading',
|
|
tags: ['#tag'],
|
|
properties: { author: 'Ada', status: 'draft' },
|
|
lines: ['Hello world with #tag', 'Another line'],
|
|
blocks: ['Hello world with #tag in section heading'],
|
|
sections: [{ heading: 'Section', content: 'Hello world', level: 1 }],
|
|
tasks: [
|
|
{ text: 'Call Bob', completed: false, line: 3 },
|
|
{ text: 'Review PR', completed: true, line: 5 }
|
|
],
|
|
...overrides
|
|
});
|
|
|
|
runTest('parseSearchQuery marks empty query as empty', () => {
|
|
const parsed = parseSearchQuery('');
|
|
assert(parsed.isEmpty, 'Empty query should be flagged as empty');
|
|
assertEqual(parsed.ast.type, 'group', 'Empty query AST should be an empty group');
|
|
});
|
|
|
|
runTest('parseSearchQuery handles basic text queries', () => {
|
|
const parsed = parseSearchQuery('hello world');
|
|
assert(!parsed.isEmpty, 'Non-empty query should not be marked empty');
|
|
assertEqual(parsed.ast.type, 'group', 'Text query AST should be a group');
|
|
});
|
|
|
|
runTest('parseSearchQuery handles operators', () => {
|
|
const parsed = parseSearchQuery('path:notes/ tag:#tag file:example');
|
|
assert(!parsed.isEmpty, 'Operator query should parse successfully');
|
|
});
|
|
|
|
runTest('queryToPredicate matches simple text', () => {
|
|
const predicate = queryToPredicate(parseSearchQuery('hello'));
|
|
assert(predicate(createContext()), 'Predicate should match when text is present');
|
|
});
|
|
|
|
runTest('queryToPredicate respects negation', () => {
|
|
const predicate = queryToPredicate(parseSearchQuery('-deprecated'));
|
|
assert(predicate(createContext()), 'Predicate should pass when term is absent');
|
|
assert(!predicate(createContext({ content: 'deprecated feature' })), 'Predicate should fail when negated term exists');
|
|
});
|
|
|
|
runTest('queryToPredicate matches task scopes', () => {
|
|
const todoPredicate = queryToPredicate(parseSearchQuery('task-todo:Call'));
|
|
const donePredicate = queryToPredicate(parseSearchQuery('task-done:Review'));
|
|
assert(todoPredicate(createContext()), 'task-todo should match incomplete tasks');
|
|
assert(donePredicate(createContext()), 'task-done should match completed tasks');
|
|
});
|
|
|
|
runTest('detectQueryType recognises path prefix', () => {
|
|
const info = detectQueryType('path:notes');
|
|
assertEqual(info.type, 'path', 'Should recognise path prefix');
|
|
assertEqual(info.value, 'notes', 'Should capture path value');
|
|
});
|
|
|
|
runTest('detectQueryType recognises property bracket', () => {
|
|
const info = detectQueryType('[status:');
|
|
assertEqual(info.type, 'property', 'Should recognise property prefix');
|
|
});
|
|
|
|
runTest('detectQueryType falls back to general type', () => {
|
|
const info = detectQueryType('plain text');
|
|
assertEqual(info.type, 'general', 'General text should return general type');
|
|
});
|
|
|
|
export { testResults };
|