zero-query 0.6.3 → 0.8.6
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.
- package/README.md +39 -29
- package/cli/commands/build.js +113 -4
- package/cli/commands/bundle.js +392 -29
- package/cli/commands/create.js +1 -1
- package/cli/commands/dev/devtools/index.js +56 -0
- package/cli/commands/dev/devtools/js/components.js +49 -0
- package/cli/commands/dev/devtools/js/core.js +409 -0
- package/cli/commands/dev/devtools/js/elements.js +413 -0
- package/cli/commands/dev/devtools/js/network.js +166 -0
- package/cli/commands/dev/devtools/js/performance.js +73 -0
- package/cli/commands/dev/devtools/js/router.js +105 -0
- package/cli/commands/dev/devtools/js/source.js +132 -0
- package/cli/commands/dev/devtools/js/stats.js +35 -0
- package/cli/commands/dev/devtools/js/tabs.js +79 -0
- package/cli/commands/dev/devtools/panel.html +95 -0
- package/cli/commands/dev/devtools/styles.css +244 -0
- package/cli/commands/dev/index.js +29 -4
- package/cli/commands/dev/logger.js +6 -1
- package/cli/commands/dev/overlay.js +428 -2
- package/cli/commands/dev/server.js +42 -5
- package/cli/commands/dev/watcher.js +59 -1
- package/cli/help.js +8 -5
- package/cli/scaffold/{scripts → app}/app.js +16 -23
- package/cli/scaffold/{scripts → app}/components/about.js +4 -4
- package/cli/scaffold/{scripts → app}/components/api-demo.js +1 -1
- package/cli/scaffold/{scripts → app}/components/contacts/contacts.css +0 -7
- package/cli/scaffold/{scripts → app}/components/contacts/contacts.html +3 -3
- package/cli/scaffold/app/components/home.js +137 -0
- package/cli/scaffold/{scripts → app}/routes.js +1 -1
- package/cli/scaffold/{scripts → app}/store.js +6 -6
- package/cli/scaffold/assets/.gitkeep +0 -0
- package/cli/scaffold/{styles/styles.css → global.css} +4 -2
- package/cli/scaffold/index.html +12 -11
- package/cli/utils.js +111 -6
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +1122 -158
- package/dist/zquery.min.js +3 -16
- package/index.d.ts +129 -1290
- package/index.js +15 -10
- package/package.json +7 -6
- package/src/component.js +172 -49
- package/src/core.js +359 -18
- package/src/diff.js +256 -58
- package/src/expression.js +33 -3
- package/src/reactive.js +37 -5
- package/src/router.js +243 -7
- package/tests/component.test.js +886 -0
- package/tests/core.test.js +977 -0
- package/tests/diff.test.js +525 -0
- package/tests/errors.test.js +162 -0
- package/tests/expression.test.js +482 -0
- package/tests/http.test.js +289 -0
- package/tests/reactive.test.js +339 -0
- package/tests/router.test.js +649 -0
- package/tests/store.test.js +379 -0
- package/tests/utils.test.js +512 -0
- package/types/collection.d.ts +383 -0
- package/types/component.d.ts +217 -0
- package/types/errors.d.ts +103 -0
- package/types/http.d.ts +81 -0
- package/types/misc.d.ts +179 -0
- package/types/reactive.d.ts +76 -0
- package/types/router.d.ts +161 -0
- package/types/ssr.d.ts +49 -0
- package/types/store.d.ts +107 -0
- package/types/utils.d.ts +142 -0
- package/cli/commands/dev.old.js +0 -520
- package/cli/scaffold/scripts/components/home.js +0 -137
- /package/cli/scaffold/{scripts → app}/components/contacts/contacts.js +0 -0
- /package/cli/scaffold/{scripts → app}/components/counter.js +0 -0
- /package/cli/scaffold/{scripts → app}/components/not-found.js +0 -0
- /package/cli/scaffold/{scripts → app}/components/todos.js +0 -0
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { safeEval } from '../src/expression.js';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Helpers
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
const eval_ = (expr, ...scopes) => safeEval(expr, scopes.length ? scopes : [{}]);
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Literals
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
describe('expression parser — literals', () => {
|
|
16
|
+
it('numbers', () => {
|
|
17
|
+
expect(eval_('42')).toBe(42);
|
|
18
|
+
expect(eval_('3.14')).toBe(3.14);
|
|
19
|
+
expect(eval_('0xFF')).toBe(255);
|
|
20
|
+
expect(eval_('1e3')).toBe(1000);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('strings', () => {
|
|
24
|
+
expect(eval_("'hello'")).toBe('hello');
|
|
25
|
+
expect(eval_('"world"')).toBe('world');
|
|
26
|
+
expect(eval_("'it\\'s'")).toBe("it's");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('booleans and null/undefined', () => {
|
|
30
|
+
expect(eval_('true')).toBe(true);
|
|
31
|
+
expect(eval_('false')).toBe(false);
|
|
32
|
+
expect(eval_('null')).toBe(null);
|
|
33
|
+
expect(eval_('undefined')).toBe(undefined);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('empty expression returns undefined', () => {
|
|
37
|
+
expect(eval_('')).toBe(undefined);
|
|
38
|
+
expect(eval_(' ')).toBe(undefined);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Arithmetic
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
describe('expression parser — arithmetic', () => {
|
|
48
|
+
it('basic operations', () => {
|
|
49
|
+
expect(eval_('2 + 3')).toBe(5);
|
|
50
|
+
expect(eval_('10 - 4')).toBe(6);
|
|
51
|
+
expect(eval_('3 * 7')).toBe(21);
|
|
52
|
+
expect(eval_('15 / 3')).toBe(5);
|
|
53
|
+
expect(eval_('10 % 3')).toBe(1);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('operator precedence', () => {
|
|
57
|
+
expect(eval_('2 + 3 * 4')).toBe(14);
|
|
58
|
+
expect(eval_('(2 + 3) * 4')).toBe(20);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('unary operators', () => {
|
|
62
|
+
expect(eval_('-5')).toBe(-5);
|
|
63
|
+
expect(eval_('+3')).toBe(3);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Comparison & logical
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
describe('expression parser — comparison', () => {
|
|
73
|
+
it('equality', () => {
|
|
74
|
+
expect(eval_('1 === 1')).toBe(true);
|
|
75
|
+
expect(eval_('1 !== 2')).toBe(true);
|
|
76
|
+
expect(eval_("1 == '1'")).toBe(true);
|
|
77
|
+
expect(eval_("1 != '2'")).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('relational', () => {
|
|
81
|
+
expect(eval_('3 > 2')).toBe(true);
|
|
82
|
+
expect(eval_('3 < 2')).toBe(false);
|
|
83
|
+
expect(eval_('3 >= 3')).toBe(true);
|
|
84
|
+
expect(eval_('3 <= 2')).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
describe('expression parser — logical', () => {
|
|
90
|
+
it('&& and ||', () => {
|
|
91
|
+
expect(eval_('true && false')).toBe(false);
|
|
92
|
+
expect(eval_('true || false')).toBe(true);
|
|
93
|
+
expect(eval_('0 || 42')).toBe(42);
|
|
94
|
+
expect(eval_("'' || 'default'")).toBe('default');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('!', () => {
|
|
98
|
+
expect(eval_('!true')).toBe(false);
|
|
99
|
+
expect(eval_('!false')).toBe(true);
|
|
100
|
+
expect(eval_('!0')).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('nullish coalescing ??', () => {
|
|
104
|
+
expect(eval_('null ?? 10')).toBe(10);
|
|
105
|
+
expect(eval_('undefined ?? 20')).toBe(20);
|
|
106
|
+
expect(eval_('0 ?? 30')).toBe(0);
|
|
107
|
+
expect(eval_("'' ?? 'fallback'")).toBe('');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Ternary
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
describe('expression parser — ternary', () => {
|
|
117
|
+
it('evaluates truthy branch', () => {
|
|
118
|
+
expect(eval_("true ? 'yes' : 'no'")).toBe('yes');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('evaluates falsy branch', () => {
|
|
122
|
+
expect(eval_("false ? 'yes' : 'no'")).toBe('no');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('works with expressions', () => {
|
|
126
|
+
expect(eval_('5 > 3 ? 10 : 20')).toBe(10);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// Property access & scope
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
describe('expression parser — property access', () => {
|
|
136
|
+
it('reads scope variables', () => {
|
|
137
|
+
expect(eval_('x', { x: 42 })).toBe(42);
|
|
138
|
+
expect(eval_('name', { name: 'Tony' })).toBe('Tony');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('dot access', () => {
|
|
142
|
+
expect(eval_('user.name', { user: { name: 'Tony' } })).toBe('Tony');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('computed access', () => {
|
|
146
|
+
expect(eval_('items[0]', { items: ['a', 'b', 'c'] })).toBe('a');
|
|
147
|
+
expect(eval_('obj[key]', { obj: { x: 1 }, key: 'x' })).toBe(1);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('optional chaining ?.', () => {
|
|
151
|
+
expect(eval_('user?.name', { user: null })).toBe(undefined);
|
|
152
|
+
expect(eval_('user?.name', { user: { name: 'Tony' } })).toBe('Tony');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('returns undefined for missing scope keys', () => {
|
|
156
|
+
expect(eval_('missing')).toBe(undefined);
|
|
157
|
+
expect(eval_('a.b.c', { a: {} })).toBe(undefined);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
// Method calls
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
describe('expression parser — method calls', () => {
|
|
167
|
+
it('string methods', () => {
|
|
168
|
+
expect(eval_("'hello'.toUpperCase()")).toBe('HELLO');
|
|
169
|
+
expect(eval_("'hello world'.split(' ')")).toEqual(['hello', 'world']);
|
|
170
|
+
expect(eval_("'abc'.includes('b')")).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('array methods', () => {
|
|
174
|
+
expect(eval_('items.length', { items: [1, 2, 3] })).toBe(3);
|
|
175
|
+
expect(eval_('items.includes(2)', { items: [1, 2, 3] })).toBe(true);
|
|
176
|
+
expect(eval_("items.join(',')", { items: [1, 2, 3] })).toBe('1,2,3');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('custom function calls', () => {
|
|
180
|
+
const add = (a, b) => a + b;
|
|
181
|
+
expect(eval_('add(1, 2)', { add })).toBe(3);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// Built-in globals
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
describe('expression parser — built-in globals', () => {
|
|
191
|
+
it('Math', () => {
|
|
192
|
+
expect(eval_('Math.PI')).toBeCloseTo(3.14159);
|
|
193
|
+
expect(eval_('Math.max(1, 5, 3)')).toBe(5);
|
|
194
|
+
expect(eval_('Math.abs(-7)')).toBe(7);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('JSON', () => {
|
|
198
|
+
expect(eval_("JSON.parse('{\"a\":1}')")).toEqual({ a: 1 });
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('parseInt/parseFloat', () => {
|
|
202
|
+
expect(eval_("parseInt('42')")).toBe(42);
|
|
203
|
+
expect(eval_("parseFloat('3.14')")).toBeCloseTo(3.14);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('isNaN', () => {
|
|
207
|
+
expect(eval_('isNaN(NaN)')).toBe(true);
|
|
208
|
+
expect(eval_('isNaN(5)')).toBe(false);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
// Template literals
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
|
|
217
|
+
describe('expression parser — template literals', () => {
|
|
218
|
+
it('simple interpolation', () => {
|
|
219
|
+
expect(eval_('`Hello ${name}`', { name: 'Tony' })).toBe('Hello Tony');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('expression inside interpolation', () => {
|
|
223
|
+
expect(eval_('`${a + b}`', { a: 1, b: 2 })).toBe('3');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('no interpolation', () => {
|
|
227
|
+
expect(eval_('`plain text`')).toBe('plain text');
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
// Array & object literals
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
|
|
236
|
+
describe('expression parser — array/object literals', () => {
|
|
237
|
+
it('array literal', () => {
|
|
238
|
+
expect(eval_('[1, 2, 3]')).toEqual([1, 2, 3]);
|
|
239
|
+
expect(eval_('[]')).toEqual([]);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('object literal', () => {
|
|
243
|
+
expect(eval_("{ x: 1, y: 'two' }")).toEqual({ x: 1, y: 'two' });
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('shorthand property', () => {
|
|
247
|
+
expect(eval_('{ x }', { x: 42 })).toEqual({ x: 42 });
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
// Arrow functions
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
|
|
256
|
+
describe('expression parser — arrow functions', () => {
|
|
257
|
+
it('single-param arrow', () => {
|
|
258
|
+
const fn = eval_('x => x * 2');
|
|
259
|
+
expect(fn(3)).toBe(6);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('multi-param arrow', () => {
|
|
263
|
+
const fn = eval_('(a, b) => a + b');
|
|
264
|
+
expect(fn(1, 2)).toBe(3);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('no-param arrow', () => {
|
|
268
|
+
const fn = eval_('() => 42');
|
|
269
|
+
expect(fn()).toBe(42);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('arrow with scope access', () => {
|
|
273
|
+
const items = [1, 2, 3];
|
|
274
|
+
expect(eval_('items.filter(x => x > 1)', { items })).toEqual([2, 3]);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
// ---------------------------------------------------------------------------
|
|
280
|
+
// typeof
|
|
281
|
+
// ---------------------------------------------------------------------------
|
|
282
|
+
|
|
283
|
+
describe('expression parser — typeof', () => {
|
|
284
|
+
it('typeof string', () => {
|
|
285
|
+
expect(eval_("typeof 'hello'")).toBe('string');
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('typeof number', () => {
|
|
289
|
+
expect(eval_('typeof 42')).toBe('number');
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('typeof undefined variable', () => {
|
|
293
|
+
expect(eval_('typeof missing')).toBe('undefined');
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
// ---------------------------------------------------------------------------
|
|
299
|
+
// Safety / security
|
|
300
|
+
// ---------------------------------------------------------------------------
|
|
301
|
+
|
|
302
|
+
describe('expression parser — safety', () => {
|
|
303
|
+
it('blocks constructor access', () => {
|
|
304
|
+
expect(eval_("''.constructor")).toBe(undefined);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('blocks __proto__ access', () => {
|
|
308
|
+
expect(eval_("({}).__proto__", {})).toBe(undefined);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('returns undefined for invalid expressions', () => {
|
|
312
|
+
expect(eval_('!!!!')).toBe(undefined);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('handles deeply nested safe access', () => {
|
|
316
|
+
const data = { a: { b: { c: { d: 'deep' } } } };
|
|
317
|
+
expect(eval_('a.b.c.d', data)).toBe('deep');
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
// Multi-scope resolution
|
|
324
|
+
// ---------------------------------------------------------------------------
|
|
325
|
+
|
|
326
|
+
describe('expression parser — multi-scope', () => {
|
|
327
|
+
it('checks scope layers in order', () => {
|
|
328
|
+
expect(safeEval('x', [{ x: 'first' }, { x: 'second' }])).toBe('first');
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('falls through to second scope', () => {
|
|
332
|
+
expect(safeEval('y', [{ x: 1 }, { y: 2 }])).toBe(2);
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
// ---------------------------------------------------------------------------
|
|
338
|
+
// in operator
|
|
339
|
+
// ---------------------------------------------------------------------------
|
|
340
|
+
|
|
341
|
+
describe('expression parser — in operator', () => {
|
|
342
|
+
it('checks property existence', () => {
|
|
343
|
+
expect(eval_("'x' in obj", { obj: { x: 1 } })).toBe(true);
|
|
344
|
+
expect(eval_("'y' in obj", { obj: { x: 1 } })).toBe(false);
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
// ---------------------------------------------------------------------------
|
|
350
|
+
// instanceof operator
|
|
351
|
+
// ---------------------------------------------------------------------------
|
|
352
|
+
|
|
353
|
+
describe('expression parser — instanceof', () => {
|
|
354
|
+
it('checks instanceOf', () => {
|
|
355
|
+
expect(eval_('arr instanceof Array', { arr: [1, 2] })).toBe(true);
|
|
356
|
+
expect(eval_('obj instanceof Array', { obj: {} })).toBe(false);
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
// ---------------------------------------------------------------------------
|
|
362
|
+
// Nested ternary
|
|
363
|
+
// ---------------------------------------------------------------------------
|
|
364
|
+
|
|
365
|
+
describe('expression parser — nested ternary', () => {
|
|
366
|
+
it('evaluates simple ternary correctly', () => {
|
|
367
|
+
expect(eval_("x > 10 ? 'big' : 'small'", { x: 12 })).toBe('big');
|
|
368
|
+
expect(eval_("x > 10 ? 'big' : 'small'", { x: 2 })).toBe('small');
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
// ---------------------------------------------------------------------------
|
|
374
|
+
// Chained method calls
|
|
375
|
+
// ---------------------------------------------------------------------------
|
|
376
|
+
|
|
377
|
+
describe('expression parser — chained calls', () => {
|
|
378
|
+
it('chains array methods', () => {
|
|
379
|
+
expect(eval_('items.filter(x => x > 1).map(x => x * 2)', { items: [1, 2, 3] })).toEqual([4, 6]);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('chains string methods', () => {
|
|
383
|
+
expect(eval_("name.trim().toUpperCase()", { name: ' hello ' })).toBe('HELLO');
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
// ---------------------------------------------------------------------------
|
|
389
|
+
// Spread operator
|
|
390
|
+
// ---------------------------------------------------------------------------
|
|
391
|
+
|
|
392
|
+
describe('expression parser — spread / rest', () => {
|
|
393
|
+
it('spread is not supported — returns gracefully', () => {
|
|
394
|
+
// The parser does not support spread syntax; verify it doesn't throw
|
|
395
|
+
const result = eval_('[...items, 4]', { items: [1, 2, 3] });
|
|
396
|
+
expect(result).toBeDefined();
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
// ---------------------------------------------------------------------------
|
|
402
|
+
// Destructuring assignment in arrow body
|
|
403
|
+
// ---------------------------------------------------------------------------
|
|
404
|
+
|
|
405
|
+
describe('expression parser — complex arrow', () => {
|
|
406
|
+
it('arrow as callback in array method', () => {
|
|
407
|
+
const items = [{ n: 'a' }, { n: 'b' }];
|
|
408
|
+
expect(eval_('items.map(x => x.n)', { items })).toEqual(['a', 'b']);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it('arrow with ternary body', () => {
|
|
412
|
+
const fn = eval_('x => x > 0 ? "pos" : "neg"');
|
|
413
|
+
expect(fn(1)).toBe('pos');
|
|
414
|
+
expect(fn(-1)).toBe('neg');
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
// ---------------------------------------------------------------------------
|
|
420
|
+
// Bitwise operators
|
|
421
|
+
// ---------------------------------------------------------------------------
|
|
422
|
+
|
|
423
|
+
describe('expression parser — bitwise', () => {
|
|
424
|
+
it('bitwise operators are not supported — does not throw', () => {
|
|
425
|
+
// The expression parser does not implement bitwise operators
|
|
426
|
+
// Verify graceful fallback rather than crashes
|
|
427
|
+
expect(() => eval_('5 | 3')).not.toThrow();
|
|
428
|
+
expect(() => eval_('5 & 3')).not.toThrow();
|
|
429
|
+
expect(() => eval_('5 ^ 3')).not.toThrow();
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
// ---------------------------------------------------------------------------
|
|
435
|
+
// Comma expressions
|
|
436
|
+
// ---------------------------------------------------------------------------
|
|
437
|
+
|
|
438
|
+
describe('expression parser — comma', () => {
|
|
439
|
+
it('comma expressions are not supported — does not throw', () => {
|
|
440
|
+
// The parser does not support comma expressions
|
|
441
|
+
expect(() => eval_('(1, 2, 3)')).not.toThrow();
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
// ---------------------------------------------------------------------------
|
|
447
|
+
// Edge cases
|
|
448
|
+
// ---------------------------------------------------------------------------
|
|
449
|
+
|
|
450
|
+
describe('expression parser — edge cases', () => {
|
|
451
|
+
it('handles very long dot chains', () => {
|
|
452
|
+
const data = { a: { b: { c: { d: { e: 42 } } } } };
|
|
453
|
+
expect(eval_('a.b.c.d.e', data)).toBe(42);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it('handles numeric string keys in bracket access', () => {
|
|
457
|
+
expect(eval_("items['0']", { items: ['a', 'b'] })).toBe('a');
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('handles conditional access chains', () => {
|
|
461
|
+
expect(eval_('a?.b?.c', { a: null })).toBe(undefined);
|
|
462
|
+
expect(eval_('a?.b?.c', { a: { b: { c: 1 } } })).toBe(1);
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it('handles string with special characters', () => {
|
|
466
|
+
expect(eval_("'hello\\nworld'")).toContain('hello');
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it('handles negative numbers in expressions', () => {
|
|
470
|
+
expect(eval_('-1 + -2')).toBe(-3);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it('exponentiation ** is not supported — does not throw', () => {
|
|
474
|
+
// The parser does not implement ** operator
|
|
475
|
+
expect(() => eval_('2 ** 3')).not.toThrow();
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it('handles empty array/object', () => {
|
|
479
|
+
expect(eval_('[]')).toEqual([]);
|
|
480
|
+
expect(eval_('{}')).toEqual({});
|
|
481
|
+
});
|
|
482
|
+
});
|