forked from colbymchenry/codegraph
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch-query-parser.test.ts
More file actions
142 lines (118 loc) · 4.92 KB
/
Copy pathsearch-query-parser.test.ts
File metadata and controls
142 lines (118 loc) · 4.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/**
* Unit tests for the field-qualified query parser and bounded
* edit distance — the two algorithms behind `kind:`/`lang:`/`path:`/
* `name:` filtering and the fuzzy typo fallback.
*/
import { describe, it, expect } from 'vitest';
import { parseQuery, boundedEditDistance } from '../src/search/query-parser';
describe('parseQuery', () => {
it('returns plain text for a query with no field prefixes', () => {
const r = parseQuery('authenticate user');
expect(r.text).toBe('authenticate user');
expect(r.kinds).toEqual([]);
expect(r.languages).toEqual([]);
expect(r.pathFilters).toEqual([]);
expect(r.nameFilters).toEqual([]);
});
it('extracts kind: filter and removes it from text', () => {
const r = parseQuery('kind:function auth');
expect(r.kinds).toEqual(['function']);
expect(r.text).toBe('auth');
});
it('extracts lang: and language: as the same filter family', () => {
const a = parseQuery('lang:typescript foo');
const b = parseQuery('language:typescript foo');
expect(a.languages).toEqual(['typescript']);
expect(b.languages).toEqual(['typescript']);
});
it('handles multiple kind: filters as an OR set', () => {
const r = parseQuery('kind:function kind:method auth');
expect(r.kinds.sort()).toEqual(['function', 'method']);
});
it('extracts path: and name: as substring filters (kept verbatim)', () => {
const r = parseQuery('path:src/api name:Handler');
expect(r.pathFilters).toEqual(['src/api']);
expect(r.nameFilters).toEqual(['Handler']);
});
it('preserves quoted spans as a single token (whitespace in path:)', () => {
const r = parseQuery('path:"my dir/file" foo');
expect(r.pathFilters).toEqual(['my dir/file']);
expect(r.text).toBe('foo');
});
it('passes URL-like tokens through to text (does not match http: as a field)', () => {
const r = parseQuery('http://example.com');
expect(r.text).toBe('http://example.com');
expect(r.kinds).toEqual([]);
});
it('passes empty-value tokens through as text (kind: → "kind:")', () => {
const r = parseQuery('kind: foo');
expect(r.kinds).toEqual([]);
// The trailing-colon token comes back as plain text
expect(r.text.includes('kind:')).toBe(true);
});
it('passes unknown field prefixes through as text (TODO: keeps the colon)', () => {
const r = parseQuery('TODO: needs review');
expect(r.text).toBe('TODO: needs review');
expect(r.kinds).toEqual([]);
});
it('rejects unknown values for kind: (passes the whole token to text)', () => {
const r = parseQuery('kind:invalid foo');
// Invalid kind value falls back to text
expect(r.kinds).toEqual([]);
expect(r.text).toContain('kind:invalid');
});
it('handles all-filters-no-text query', () => {
const r = parseQuery('kind:function lang:typescript');
expect(r.kinds).toEqual(['function']);
expect(r.languages).toEqual(['typescript']);
expect(r.text).toBe('');
});
it('survives empty input', () => {
const r = parseQuery('');
expect(r.text).toBe('');
expect(r.kinds).toEqual([]);
});
it('survives a very long input (no allocation explosion)', () => {
const huge = 'foo '.repeat(5000); // 20k chars
const r = parseQuery(huge);
expect(r.text.length).toBeGreaterThan(0);
});
});
describe('boundedEditDistance', () => {
it('returns 0 for identical strings', () => {
expect(boundedEditDistance('user', 'user', 2)).toBe(0);
});
it('returns 1 for a single substitution', () => {
expect(boundedEditDistance('user', 'usar', 2)).toBe(1);
});
it('returns 1 for a single insertion', () => {
expect(boundedEditDistance('user', 'users', 2)).toBe(1);
});
it('returns 1 for a single deletion', () => {
expect(boundedEditDistance('users', 'user', 2)).toBe(1);
});
it('returns 2 for a transposition (two edits in basic Levenshtein)', () => {
// 'aple' vs 'palp' would be 2; pick a clearer pair.
// 'foo' vs 'fou': substitution + insertion = 2 if different lengths.
expect(boundedEditDistance('confg', 'configX', 2)).toBe(2);
});
it('returns maxDist+1 when distance clearly exceeds budget', () => {
expect(boundedEditDistance('foo', 'completely-different', 2)).toBe(3);
});
it('respects length-difference shortcut', () => {
// |len(a) - len(b)| > maxDist must immediately be over budget
expect(boundedEditDistance('a', 'aaaaaaa', 2)).toBe(3);
});
it('handles empty inputs', () => {
expect(boundedEditDistance('', '', 2)).toBe(0);
expect(boundedEditDistance('a', '', 2)).toBe(1);
expect(boundedEditDistance('', 'abc', 2)).toBe(3);
});
it('is case-sensitive — caller must lowercase if case-insensitive match wanted', () => {
expect(boundedEditDistance('Foo', 'foo', 2)).toBe(1);
});
it('early-exits when row min exceeds budget (correctness, not just perf)', () => {
// 'aaaaa' vs 'bbbbb': distance is 5, well over budget 2
expect(boundedEditDistance('aaaaa', 'bbbbb', 2)).toBe(3);
});
});