@fincity/kirun-js 3.13.0 → 3.14.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.
- package/__tests__/engine/runtime/expression/ExpressionEvaluatorBracketNotationTest.ts +127 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/module.js +1 -1
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/engine/runtime/KIRuntime.ts +1 -1
- package/src/engine/runtime/expression/ExpressionEvaluator.ts +2 -0
- package/src/engine/runtime/expression/tokenextractor/TokenValueExtractor.ts +29 -0
|
@@ -256,6 +256,133 @@ describe('ExpressionEvaluator with Bracket Notation', () => {
|
|
|
256
256
|
});
|
|
257
257
|
});
|
|
258
258
|
|
|
259
|
+
// Keys whose characters collide with operators. `http-equiv` is the real
|
|
260
|
+
// case: it is one of the four attributes a `properties.metas.<uid>` entry
|
|
261
|
+
// can carry in an app definition, and a hyphen is also subtraction.
|
|
262
|
+
describe('Keys containing operator characters', () => {
|
|
263
|
+
beforeEach(() => {
|
|
264
|
+
testData.obj['key-with-hyphen'] = 'hyphenValue';
|
|
265
|
+
testData.obj['key with space'] = 'spaceValue';
|
|
266
|
+
testData.obj['key+plus'] = 'plusValue';
|
|
267
|
+
testData.obj['key:colon'] = 'colonValue';
|
|
268
|
+
testData.obj['key/slash'] = 'slashValue';
|
|
269
|
+
testData.obj['a-b'] = 'literalAMinusB';
|
|
270
|
+
testData.obj.a = 10;
|
|
271
|
+
testData.obj.b = 3;
|
|
272
|
+
testData.metas = { m1: { 'http-equiv': 'X-UA-Compatible', content: 'IE=edge' } };
|
|
273
|
+
testData.k = 'm1';
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test('hyphen in a double-quoted key', () => {
|
|
277
|
+
expect(
|
|
278
|
+
new ExpressionEvaluator('Context.obj["key-with-hyphen"]').evaluate(evaluatorMap),
|
|
279
|
+
).toBe('hyphenValue');
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test('hyphen in a single-quoted key', () => {
|
|
283
|
+
expect(
|
|
284
|
+
new ExpressionEvaluator("Context.obj['key-with-hyphen']").evaluate(evaluatorMap),
|
|
285
|
+
).toBe('hyphenValue');
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test('http-equiv, the app definition meta attribute', () => {
|
|
289
|
+
expect(
|
|
290
|
+
new ExpressionEvaluator('Context.metas.m1["http-equiv"]').evaluate(evaluatorMap),
|
|
291
|
+
).toBe('X-UA-Compatible');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test('space, plus, colon and slash in a key', () => {
|
|
295
|
+
expect(
|
|
296
|
+
new ExpressionEvaluator('Context.obj["key with space"]').evaluate(evaluatorMap),
|
|
297
|
+
).toBe('spaceValue');
|
|
298
|
+
expect(new ExpressionEvaluator('Context.obj["key+plus"]').evaluate(evaluatorMap)).toBe(
|
|
299
|
+
'plusValue',
|
|
300
|
+
);
|
|
301
|
+
expect(new ExpressionEvaluator('Context.obj["key:colon"]').evaluate(evaluatorMap)).toBe(
|
|
302
|
+
'colonValue',
|
|
303
|
+
);
|
|
304
|
+
expect(new ExpressionEvaluator('Context.obj["key/slash"]').evaluate(evaluatorMap)).toBe(
|
|
305
|
+
'slashValue',
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test('a quoted key wins over the arithmetic it looks like', () => {
|
|
310
|
+
expect(new ExpressionEvaluator('Context.obj["a-b"]').evaluate(evaluatorMap)).toBe(
|
|
311
|
+
'literalAMinusB',
|
|
312
|
+
);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test('the SAME key unquoted is arithmetic, and throws', () => {
|
|
316
|
+
expect(() =>
|
|
317
|
+
new ExpressionEvaluator('Context.obj.key-with-hyphen').evaluate(evaluatorMap),
|
|
318
|
+
).toThrow();
|
|
319
|
+
expect(() =>
|
|
320
|
+
new ExpressionEvaluator('Context.metas.m1.http-equiv').evaluate(evaluatorMap),
|
|
321
|
+
).toThrow();
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test('chained brackets, both keys hyphenated or dotted', () => {
|
|
325
|
+
expect(
|
|
326
|
+
new ExpressionEvaluator("Context.metas['m1']['http-equiv']").evaluate(evaluatorMap),
|
|
327
|
+
).toBe('X-UA-Compatible');
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test('a variable key resolves, including into a hyphenated leaf', () => {
|
|
331
|
+
expect(
|
|
332
|
+
new ExpressionEvaluator('Context.metas[Context.k].content').evaluate(evaluatorMap),
|
|
333
|
+
).toBe('IE=edge');
|
|
334
|
+
expect(
|
|
335
|
+
new ExpressionEvaluator("Context.metas[Context.k]['http-equiv']").evaluate(
|
|
336
|
+
evaluatorMap,
|
|
337
|
+
),
|
|
338
|
+
).toBe('X-UA-Compatible');
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// LIMITATION, asserted so a fix shows up as a failing test rather than
|
|
342
|
+
// going unnoticed: a bracket segment is a literal or a token path, never
|
|
343
|
+
// an expression. A concatenation inside brackets is taken as the key
|
|
344
|
+
// text itself and silently misses, with no error.
|
|
345
|
+
test('an expression inside brackets is NOT evaluated (documented gap)', () => {
|
|
346
|
+
expect(
|
|
347
|
+
new ExpressionEvaluator("Context.metas['m' + '1'].content").evaluate(evaluatorMap),
|
|
348
|
+
).toBeUndefined();
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// A token whose FIRST separator is a bracket used to match no extractor at
|
|
353
|
+
// all: the prefix is taken as the text up to the first dot, so `Context[0]`
|
|
354
|
+
// produced an empty prefix and fell through to the literal extractor, which
|
|
355
|
+
// throws. In the UI that throw reached the root error boundary and replaced
|
|
356
|
+
// the whole page with an error card. `Parent[0]` was the case that surfaced
|
|
357
|
+
// it, from a repeater bound to ObjectEntries output ([key, value] pairs),
|
|
358
|
+
// but it was never Parent-specific.
|
|
359
|
+
describe('Root level bracket access', () => {
|
|
360
|
+
test('numeric index directly on the extractor root', () => {
|
|
361
|
+
const arr: any = ['zero', 'one', 'two'];
|
|
362
|
+
const map = new Map([['Context.', new TestExtractor(arr)]]);
|
|
363
|
+
expect(new ExpressionEvaluator('Context[0]').evaluate(map)).toBe('zero');
|
|
364
|
+
expect(new ExpressionEvaluator('Context[2]').evaluate(map)).toBe('two');
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
test('the dotted form keeps working', () => {
|
|
368
|
+
const arr: any = ['zero', 'one', 'two'];
|
|
369
|
+
const map = new Map([['Context.', new TestExtractor(arr)]]);
|
|
370
|
+
expect(new ExpressionEvaluator('Context.0').evaluate(map)).toBe('zero');
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
test('quoted key directly on the extractor root', () => {
|
|
374
|
+
const map = new Map([['Context.', new TestExtractor({ 'a.b': 'dotted', plain: 'p' })]]);
|
|
375
|
+
expect(new ExpressionEvaluator('Context["a.b"]').evaluate(map)).toBe('dotted');
|
|
376
|
+
expect(new ExpressionEvaluator('Context["plain"]').evaluate(map)).toBe('p');
|
|
377
|
+
expect(new ExpressionEvaluator("Context['plain']").evaluate(map)).toBe('p');
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test('a bracket after a dotted path is untouched', () => {
|
|
381
|
+
const map = new Map([['Context.', new TestExtractor({ arr: [1, 2, 3] })]]);
|
|
382
|
+
expect(new ExpressionEvaluator('Context.arr[1]').evaluate(map)).toBe(2);
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
259
386
|
describe('Edge cases', () => {
|
|
260
387
|
test('should handle property with multiple dots', () => {
|
|
261
388
|
testData.obj['a.b.c.d'] = 'deepValue';
|