@gblikas/querykit 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,341 @@
1
+ /**
2
+ * Tests to verify that parseWithContext returns the same token information
3
+ * as the standalone input parser (parseQueryTokens).
4
+ *
5
+ * This ensures the integration is correct and both approaches produce
6
+ * consistent results.
7
+ */
8
+
9
+ import { QueryParser } from './parser';
10
+ import { parseQueryTokens, parseQueryInput } from './input-parser';
11
+
12
+ describe('Token Consistency: parseWithContext vs input parser', () => {
13
+ const parser = new QueryParser();
14
+
15
+ describe('token count consistency', () => {
16
+ const testCases = [
17
+ 'status:done',
18
+ 'status:done AND priority:high',
19
+ 'a:1 OR b:2 OR c:3',
20
+ 'status:done AND priority:high OR assigned:me',
21
+ '(a:1 AND b:2)',
22
+ 'status:',
23
+ 'status:d',
24
+ '',
25
+ ' ',
26
+ 'hello world',
27
+ '-status:active',
28
+ 'name:"John Doe"'
29
+ ];
30
+
31
+ testCases.forEach(input => {
32
+ it(`should have same token count for: "${input}"`, () => {
33
+ const contextResult = parser.parseWithContext(input);
34
+ const inputParserResult = parseQueryTokens(input);
35
+
36
+ expect(contextResult.tokens.length).toBe(
37
+ inputParserResult.tokens.length
38
+ );
39
+ });
40
+ });
41
+ });
42
+
43
+ describe('token content consistency', () => {
44
+ it('should have identical term tokens', () => {
45
+ const input = 'status:done AND priority:high';
46
+
47
+ const contextResult = parser.parseWithContext(input);
48
+ const inputParserResult = parseQueryTokens(input);
49
+
50
+ // Compare each token
51
+ for (let i = 0; i < contextResult.tokens.length; i++) {
52
+ const ctxToken = contextResult.tokens[i];
53
+ const ipToken = inputParserResult.tokens[i];
54
+
55
+ expect(ctxToken.type).toBe(ipToken.type);
56
+ expect(ctxToken.startPosition).toBe(ipToken.startPosition);
57
+ expect(ctxToken.endPosition).toBe(ipToken.endPosition);
58
+ expect(ctxToken.raw).toBe(ipToken.raw);
59
+
60
+ if (ctxToken.type === 'term' && ipToken.type === 'term') {
61
+ expect(ctxToken.key).toBe(ipToken.key);
62
+ expect(ctxToken.operator).toBe(ipToken.operator);
63
+ expect(ctxToken.value).toBe(ipToken.value);
64
+ }
65
+
66
+ if (ctxToken.type === 'operator' && ipToken.type === 'operator') {
67
+ expect(ctxToken.operator).toBe(ipToken.operator);
68
+ }
69
+ }
70
+ });
71
+
72
+ it('should have identical operator tokens', () => {
73
+ const input = 'a:1 AND b:2 OR c:3 NOT d:4';
74
+
75
+ const contextResult = parser.parseWithContext(input);
76
+ const inputParserResult = parseQueryTokens(input);
77
+
78
+ const ctxOperators = contextResult.tokens.filter(
79
+ t => t.type === 'operator'
80
+ );
81
+ const ipOperators = inputParserResult.tokens.filter(
82
+ t => t.type === 'operator'
83
+ );
84
+
85
+ expect(ctxOperators.length).toBe(ipOperators.length);
86
+
87
+ for (let i = 0; i < ctxOperators.length; i++) {
88
+ expect(ctxOperators[i]).toEqual(ipOperators[i]);
89
+ }
90
+ });
91
+ });
92
+
93
+ describe('position consistency', () => {
94
+ const testCases = [
95
+ 'status:done',
96
+ 'a:1 AND b:2',
97
+ 'name:"hello world"',
98
+ '(status:active OR status:pending)',
99
+ 'priority:>5 AND count:<=10'
100
+ ];
101
+
102
+ testCases.forEach(input => {
103
+ it(`should have identical positions for: "${input}"`, () => {
104
+ const contextResult = parser.parseWithContext(input);
105
+ const inputParserResult = parseQueryTokens(input);
106
+
107
+ for (let i = 0; i < contextResult.tokens.length; i++) {
108
+ const ctxToken = contextResult.tokens[i];
109
+ const ipToken = inputParserResult.tokens[i];
110
+
111
+ expect(ctxToken.startPosition).toBe(ipToken.startPosition);
112
+ expect(ctxToken.endPosition).toBe(ipToken.endPosition);
113
+
114
+ // Verify raw text matches the slice
115
+ expect(ctxToken.raw).toBe(
116
+ input.substring(ctxToken.startPosition, ctxToken.endPosition)
117
+ );
118
+ }
119
+ });
120
+ });
121
+ });
122
+
123
+ describe('cursor/active token consistency', () => {
124
+ it('should identify same active token at cursor position', () => {
125
+ const input = 'status:done AND priority:high';
126
+ const cursorPosition = 5; // in "status"
127
+
128
+ const contextResult = parser.parseWithContext(input, { cursorPosition });
129
+ const inputParserResult = parseQueryTokens(input, cursorPosition);
130
+
131
+ // Both should identify the same active token
132
+ expect(contextResult.activeTokenIndex).toBe(
133
+ inputParserResult.activeTokenIndex
134
+ );
135
+
136
+ if (contextResult.activeToken && inputParserResult.activeToken) {
137
+ expect(contextResult.activeToken.type).toBe(
138
+ inputParserResult.activeToken.type
139
+ );
140
+ expect(contextResult.activeToken.startPosition).toBe(
141
+ inputParserResult.activeToken.startPosition
142
+ );
143
+ expect(contextResult.activeToken.endPosition).toBe(
144
+ inputParserResult.activeToken.endPosition
145
+ );
146
+ }
147
+ });
148
+
149
+ it('should identify active token in operator', () => {
150
+ const input = 'status:done AND priority:high';
151
+ const cursorPosition = 13; // in "AND"
152
+
153
+ const contextResult = parser.parseWithContext(input, { cursorPosition });
154
+ const inputParserResult = parseQueryTokens(input, cursorPosition);
155
+
156
+ expect(contextResult.activeToken?.type).toBe('operator');
157
+ expect(inputParserResult.activeToken?.type).toBe('operator');
158
+ expect(contextResult.activeTokenIndex).toBe(
159
+ inputParserResult.activeTokenIndex
160
+ );
161
+ });
162
+
163
+ it('should return same activeTokenIndex for various positions', () => {
164
+ const input = 'a:1 AND b:2 OR c:3';
165
+ const positions = [0, 1, 2, 4, 5, 8, 9, 12, 15, 18];
166
+
167
+ for (const pos of positions) {
168
+ const contextResult = parser.parseWithContext(input, {
169
+ cursorPosition: pos
170
+ });
171
+ const inputParserResult = parseQueryTokens(input, pos);
172
+
173
+ expect(contextResult.activeTokenIndex).toBe(
174
+ inputParserResult.activeTokenIndex
175
+ );
176
+ }
177
+ });
178
+ });
179
+
180
+ describe('parseQueryInput term consistency', () => {
181
+ it('should have consistent term information with parseQueryInput', () => {
182
+ const input = 'status:done AND priority:high';
183
+
184
+ const contextResult = parser.parseWithContext(input);
185
+ const inputResult = parseQueryInput(input);
186
+
187
+ // Get term tokens from parseWithContext
188
+ const ctxTerms = contextResult.tokens.filter(t => t.type === 'term');
189
+
190
+ // Compare with parseQueryInput terms
191
+ expect(ctxTerms.length).toBe(inputResult.terms.length);
192
+
193
+ for (let i = 0; i < ctxTerms.length; i++) {
194
+ const ctxTerm = ctxTerms[i];
195
+ const ipTerm = inputResult.terms[i];
196
+
197
+ if (ctxTerm.type === 'term') {
198
+ expect(ctxTerm.key).toBe(ipTerm.key);
199
+ expect(ctxTerm.value).toBe(ipTerm.value);
200
+ expect(ctxTerm.operator).toBe(ipTerm.operator);
201
+ expect(ctxTerm.startPosition).toBe(ipTerm.startPosition);
202
+ expect(ctxTerm.endPosition).toBe(ipTerm.endPosition);
203
+ expect(ctxTerm.raw).toBe(ipTerm.raw);
204
+ }
205
+ }
206
+ });
207
+
208
+ it('should have consistent logical operator information', () => {
209
+ const input = 'a:1 AND b:2 OR c:3';
210
+
211
+ const contextResult = parser.parseWithContext(input);
212
+ const inputResult = parseQueryInput(input);
213
+
214
+ // Get operator tokens from parseWithContext
215
+ const ctxOps = contextResult.tokens.filter(t => t.type === 'operator');
216
+
217
+ // Compare with parseQueryInput logical operators
218
+ expect(ctxOps.length).toBe(inputResult.logicalOperators.length);
219
+
220
+ for (let i = 0; i < ctxOps.length; i++) {
221
+ const ctxOp = ctxOps[i];
222
+ const ipOp = inputResult.logicalOperators[i];
223
+
224
+ if (ctxOp.type === 'operator') {
225
+ expect(ctxOp.operator).toBe(ipOp.operator);
226
+ expect(ctxOp.startPosition).toBe(ipOp.position);
227
+ }
228
+ }
229
+ });
230
+ });
231
+
232
+ describe('edge case consistency', () => {
233
+ it('should handle empty input consistently', () => {
234
+ const input = '';
235
+
236
+ const contextResult = parser.parseWithContext(input);
237
+ const inputParserResult = parseQueryTokens(input);
238
+
239
+ expect(contextResult.tokens).toEqual(inputParserResult.tokens);
240
+ expect(contextResult.tokens.length).toBe(0);
241
+ });
242
+
243
+ it('should handle incomplete input consistently', () => {
244
+ const input = 'status:';
245
+
246
+ const contextResult = parser.parseWithContext(input);
247
+ const inputParserResult = parseQueryTokens(input);
248
+
249
+ expect(contextResult.tokens.length).toBe(inputParserResult.tokens.length);
250
+ expect(contextResult.tokens[0]).toEqual(inputParserResult.tokens[0]);
251
+ });
252
+
253
+ it('should handle invalid input consistently', () => {
254
+ const input = 'status:done AND';
255
+
256
+ const contextResult = parser.parseWithContext(input);
257
+ const inputParserResult = parseQueryTokens(input);
258
+
259
+ expect(contextResult.tokens.length).toBe(inputParserResult.tokens.length);
260
+
261
+ // Both should have the same tokens even though parsing failed
262
+ for (let i = 0; i < contextResult.tokens.length; i++) {
263
+ expect(contextResult.tokens[i]).toEqual(inputParserResult.tokens[i]);
264
+ }
265
+ });
266
+
267
+ it('should handle quoted values consistently', () => {
268
+ const input = 'name:"John Doe" AND status:active';
269
+
270
+ const contextResult = parser.parseWithContext(input);
271
+ const inputParserResult = parseQueryTokens(input);
272
+
273
+ expect(contextResult.tokens.length).toBe(inputParserResult.tokens.length);
274
+
275
+ for (let i = 0; i < contextResult.tokens.length; i++) {
276
+ expect(contextResult.tokens[i]).toEqual(inputParserResult.tokens[i]);
277
+ }
278
+ });
279
+
280
+ it('should handle comparison operators consistently', () => {
281
+ const input = 'priority:>5 AND count:<=10';
282
+
283
+ const contextResult = parser.parseWithContext(input);
284
+ const inputParserResult = parseQueryTokens(input);
285
+
286
+ expect(contextResult.tokens.length).toBe(inputParserResult.tokens.length);
287
+
288
+ for (let i = 0; i < contextResult.tokens.length; i++) {
289
+ expect(contextResult.tokens[i]).toEqual(inputParserResult.tokens[i]);
290
+ }
291
+ });
292
+
293
+ it('should handle negation consistently', () => {
294
+ const input = '-status:inactive';
295
+
296
+ const contextResult = parser.parseWithContext(input);
297
+ const inputParserResult = parseQueryTokens(input);
298
+
299
+ expect(contextResult.tokens.length).toBe(inputParserResult.tokens.length);
300
+ expect(contextResult.tokens[0]).toEqual(inputParserResult.tokens[0]);
301
+ });
302
+ });
303
+
304
+ describe('structure vs parseQueryInput consistency', () => {
305
+ it('should have consistent isComplete', () => {
306
+ const testCases = [
307
+ { input: 'status:done', expectedComplete: true },
308
+ { input: 'status:', expectedComplete: false },
309
+ { input: 'status:done AND', expectedComplete: false },
310
+ { input: '(status:done', expectedComplete: false },
311
+ { input: 'name:"John', expectedComplete: false }
312
+ ];
313
+
314
+ for (const { input, expectedComplete } of testCases) {
315
+ const contextResult = parser.parseWithContext(input);
316
+
317
+ expect(contextResult.structure.isComplete).toBe(expectedComplete);
318
+ }
319
+ });
320
+
321
+ it('should have consistent referenced fields', () => {
322
+ const input = 'status:done AND priority:high OR status:pending';
323
+
324
+ const contextResult = parser.parseWithContext(input);
325
+ const inputResult = parseQueryInput(input);
326
+
327
+ // Get unique fields from input parser
328
+ const ipFields = [
329
+ ...new Set(
330
+ inputResult.terms
331
+ .filter(t => t.key !== null)
332
+ .map(t => t.key as string)
333
+ )
334
+ ];
335
+
336
+ expect(contextResult.structure.referencedFields.sort()).toEqual(
337
+ ipFields.sort()
338
+ );
339
+ });
340
+ });
341
+ });