@schukai/monster 4.153.1 → 4.154.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/package.json +1 -1
- package/source/components/form/message-state-button.mjs +1 -1
- package/source/components/form/select-link.mjs +12 -1
- package/source/components/state/log.mjs +3 -7
- package/source/components/state/thread.mjs +3 -7
- package/source/components/time/day.mjs +0 -19
- package/source/components/time/month-calendar.mjs +5 -1
- package/source/data/buildmap.mjs +3 -3
- package/source/data/datasource.mjs +3 -10
- package/source/data/diff.mjs +13 -12
- package/source/data/extend.mjs +23 -1
- package/source/data/pathfinder.mjs +186 -19
- package/source/dom/customelement.mjs +273 -30
- package/source/dom/updater.mjs +6 -2
- package/source/types/base.mjs +7 -6
- package/source/types/basewithoptions.mjs +1 -8
- package/source/types/internal.mjs +4 -10
- package/source/types/observerlist.mjs +9 -17
- package/source/types/proxyobserver.mjs +19 -2
- package/source/types/tokenlist.mjs +13 -8
- package/source/util/clone.mjs +44 -14
- package/test/cases/browser-bundle.mjs +75 -0
- package/test/cases/components/time/month-calendar.mjs +115 -0
- package/test/cases/data/diff.mjs +86 -0
- package/test/cases/data/extend.mjs +52 -0
- package/test/cases/data/pathfinder-resolution-baseline.mjs +322 -0
- package/test/cases/data/pathfinder.mjs +129 -0
- package/test/cases/dom/customelement-rebind-baseline.mjs +323 -0
- package/test/cases/dom/customelement-teardown.mjs +408 -0
- package/test/cases/dom/customelement.mjs +354 -0
- package/test/cases/dom/dimension.mjs +34 -10
- package/test/cases/dom/updater-remove.mjs +198 -0
- package/test/cases/packaging.mjs +213 -0
- package/test/cases/types/base.mjs +50 -0
- package/test/cases/types/observerlist.mjs +103 -1
- package/test/cases/types/proxyobserver.mjs +114 -0
- package/test/cases/types/regex.mjs +4 -0
- package/test/cases/types/tokenlist.mjs +64 -1
- package/test/cases/util/clone.mjs +115 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import {expect} from "chai"
|
|
4
|
+
import {Pathfinder} from "../../../source/data/pathfinder.mjs";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Baseline for issue #576.
|
|
8
|
+
*
|
|
9
|
+
* getOption() is called about 1393 times across the library and resolves every
|
|
10
|
+
* option through Pathfinder, today by throwing and catching. Before that read
|
|
11
|
+
* path is replaced, this file freezes the resolution semantics that callers
|
|
12
|
+
* depend on. Every assertion here passes with the old implementation and must
|
|
13
|
+
* keep passing with the new one.
|
|
14
|
+
*/
|
|
15
|
+
describe('Pathfinder resolution baseline issue #576', function () {
|
|
16
|
+
|
|
17
|
+
class WithPrototypeMember {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.own = 1;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
inherited() {
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function buildSubject() {
|
|
27
|
+
const nullProto = Object.create(null);
|
|
28
|
+
nullProto.leaf = 'from-null-proto';
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
flat: 'value',
|
|
32
|
+
zero: 0,
|
|
33
|
+
emptyString: '',
|
|
34
|
+
falseValue: false,
|
|
35
|
+
nullValue: null,
|
|
36
|
+
undefinedValue: undefined,
|
|
37
|
+
nested: {
|
|
38
|
+
deep: {
|
|
39
|
+
leaf: 'deep-leaf',
|
|
40
|
+
},
|
|
41
|
+
list: ['first', 'second', 'third'],
|
|
42
|
+
objects: [{id: 1}, {id: 2}],
|
|
43
|
+
},
|
|
44
|
+
map: new Map([['key', 'map-value'], ['nested', {leaf: 'map-leaf'}]]),
|
|
45
|
+
set: new Set(['set-first', 'set-second']),
|
|
46
|
+
instance: new WithPrototypeMember(),
|
|
47
|
+
nullProto,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
describe('getVia() resolved values', function () {
|
|
52
|
+
|
|
53
|
+
[
|
|
54
|
+
['flat', 'value'],
|
|
55
|
+
['zero', 0],
|
|
56
|
+
['emptyString', ''],
|
|
57
|
+
['falseValue', false],
|
|
58
|
+
['nullValue', null],
|
|
59
|
+
['undefinedValue', undefined],
|
|
60
|
+
['nested.deep.leaf', 'deep-leaf'],
|
|
61
|
+
['nested.list.0', 'first'],
|
|
62
|
+
['nested.list.2', 'third'],
|
|
63
|
+
['nested.objects.1.id', 2],
|
|
64
|
+
['map.key', 'map-value'],
|
|
65
|
+
['map.nested.leaf', 'map-leaf'],
|
|
66
|
+
['set.0', 'set-first'],
|
|
67
|
+
['set.1', 'set-second'],
|
|
68
|
+
['instance.own', 1],
|
|
69
|
+
['nullProto.leaf', 'from-null-proto'],
|
|
70
|
+
].forEach(function (data) {
|
|
71
|
+
const path = data[0];
|
|
72
|
+
const expected = data[1];
|
|
73
|
+
|
|
74
|
+
it(`getVia(${path}) should resolve to ${JSON.stringify(expected)}`, function () {
|
|
75
|
+
expect(new Pathfinder(buildSubject()).getVia(path)).to.equal(expected);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('getVia() should resolve a prototype member', function () {
|
|
80
|
+
const value = new Pathfinder(buildSubject()).getVia('instance.inherited');
|
|
81
|
+
expect(typeof value).to.equal('function');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('getVia() should return the root for an empty path', function () {
|
|
85
|
+
const subject = buildSubject();
|
|
86
|
+
expect(new Pathfinder(subject).getVia('')).to.equal(subject);
|
|
87
|
+
expect(new Pathfinder(subject).getVia([])).to.equal(subject);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('getVia() should accept an array path and not mutate it', function () {
|
|
91
|
+
const path = ['nested', 'deep', 'leaf'];
|
|
92
|
+
expect(new Pathfinder(buildSubject()).getVia(path)).to.equal('deep-leaf');
|
|
93
|
+
expect(path).to.deep.equal(['nested', 'deep', 'leaf']);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('getVia() missing leaves resolve to undefined', function () {
|
|
98
|
+
|
|
99
|
+
[
|
|
100
|
+
'missing',
|
|
101
|
+
'nested.missing',
|
|
102
|
+
'nested.deep.missing',
|
|
103
|
+
'map.missing',
|
|
104
|
+
].forEach(function (path) {
|
|
105
|
+
it(`getVia(${path}) should resolve to undefined`, function () {
|
|
106
|
+
expect(new Pathfinder(buildSubject()).getVia(path)).to.equal(undefined);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('getVia() throws past a non traversable value', function () {
|
|
112
|
+
|
|
113
|
+
[
|
|
114
|
+
'flat.deeper',
|
|
115
|
+
'zero.deeper',
|
|
116
|
+
'nested.deep.leaf.deeper',
|
|
117
|
+
'undefinedValue.deeper',
|
|
118
|
+
'nullValue.deeper',
|
|
119
|
+
'nested.list.0.deeper',
|
|
120
|
+
].forEach(function (path) {
|
|
121
|
+
it(`getVia(${path}) should throw`, function () {
|
|
122
|
+
expect(() => new Pathfinder(buildSubject()).getVia(path)).to.throw();
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should report that the journey is not at its end', function () {
|
|
127
|
+
expect(() => new Pathfinder(buildSubject()).getVia('flat.deeper'))
|
|
128
|
+
.to.throw(Error, 'the journey is not at its end');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should reject an index with trailing characters', function () {
|
|
132
|
+
expect(() => new Pathfinder(buildSubject()).getVia('nested.list.1suffix'))
|
|
133
|
+
.to.throw(TypeError, 'value is not an integer');
|
|
134
|
+
expect(() => new Pathfinder(buildSubject()).getVia('set.1suffix'))
|
|
135
|
+
.to.throw(TypeError, 'value is not an integer');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should reject unsupported weak collections', function () {
|
|
139
|
+
expect(() => new Pathfinder({weak: new WeakSet()}).getVia('weak.entry'))
|
|
140
|
+
.to.throw(Error, 'unsupported action for this data type (WeakSet)');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should reject a non string and non array path', function () {
|
|
144
|
+
expect(() => new Pathfinder(buildSubject()).getVia(42)).to.throw(Error);
|
|
145
|
+
expect(() => new Pathfinder(buildSubject()).getVia(undefined)).to.throw(Error);
|
|
146
|
+
expect(() => new Pathfinder(buildSubject()).getVia(null)).to.throw(Error);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('exists() membership semantics', function () {
|
|
151
|
+
|
|
152
|
+
[
|
|
153
|
+
['flat', true],
|
|
154
|
+
['zero', true],
|
|
155
|
+
['emptyString', true],
|
|
156
|
+
['falseValue', true],
|
|
157
|
+
['nullValue', true],
|
|
158
|
+
['undefinedValue', true],
|
|
159
|
+
['missing', false],
|
|
160
|
+
['nested.deep.leaf', true],
|
|
161
|
+
['nested.deep.missing', false],
|
|
162
|
+
['nested.missing.leaf', false],
|
|
163
|
+
['nested.list.0', true],
|
|
164
|
+
['nested.list.2', true],
|
|
165
|
+
['nested.list.3', false],
|
|
166
|
+
['nested.objects.1.id', true],
|
|
167
|
+
['map.key', true],
|
|
168
|
+
['map.missing', false],
|
|
169
|
+
['set.0', true],
|
|
170
|
+
['set.1', true],
|
|
171
|
+
['set.2', false],
|
|
172
|
+
['instance.own', true],
|
|
173
|
+
['instance.inherited', true],
|
|
174
|
+
['nullProto.leaf', true],
|
|
175
|
+
['nullProto.missing', false],
|
|
176
|
+
['flat.deeper', false],
|
|
177
|
+
].forEach(function (data) {
|
|
178
|
+
const path = data[0];
|
|
179
|
+
const expected = data[1];
|
|
180
|
+
|
|
181
|
+
it(`exists(${path}) should be ${expected}`, function () {
|
|
182
|
+
expect(new Pathfinder(buildSubject()).exists(path)).to.equal(expected);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('exists() should report a present key whose value is undefined', function () {
|
|
187
|
+
expect(new Pathfinder({a: {b: undefined}}).exists('a.b')).to.equal(true);
|
|
188
|
+
expect(new Pathfinder({a: {b: undefined}}).exists('a.c')).to.equal(false);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// load bearing: init-options-from-attributes.mjs and
|
|
192
|
+
// set-option-from-attribute.mjs call exists() with an undefined option
|
|
193
|
+
// name for every unknown data-monster-option-* attribute
|
|
194
|
+
it('exists() should return false instead of throwing for a malformed path', function () {
|
|
195
|
+
const pathfinder = new Pathfinder(buildSubject());
|
|
196
|
+
|
|
197
|
+
expect(pathfinder.exists(undefined)).to.equal(false);
|
|
198
|
+
expect(pathfinder.exists(null)).to.equal(false);
|
|
199
|
+
expect(pathfinder.exists(42)).to.equal(false);
|
|
200
|
+
expect(pathfinder.exists({})).to.equal(false);
|
|
201
|
+
expect(pathfinder.exists('nested.list.1suffix')).to.equal(false);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('exists() should accept an array path', function () {
|
|
205
|
+
expect(new Pathfinder(buildSubject()).exists(['nested', 'deep', 'leaf'])).to.equal(true);
|
|
206
|
+
expect(new Pathfinder(buildSubject()).exists(['nested', 'deep', 'nope'])).to.equal(false);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('setVia() vivification', function () {
|
|
211
|
+
|
|
212
|
+
it('should build nested objects', function () {
|
|
213
|
+
const subject = {};
|
|
214
|
+
new Pathfinder(subject).setVia('a.b.c', true);
|
|
215
|
+
expect(JSON.stringify(subject)).to.equal('{"a":{"b":{"c":true}}}');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should build a sparse array for an integer segment', function () {
|
|
219
|
+
const subject = {};
|
|
220
|
+
new Pathfinder(subject).setVia('a.b.3.c', true);
|
|
221
|
+
expect(JSON.stringify(subject)).to.equal('{"a":{"b":[null,null,null,{"c":true}]}}');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should build nested arrays', function () {
|
|
225
|
+
const subject = {};
|
|
226
|
+
new Pathfinder(subject).setVia('a.b.0.0.c', true);
|
|
227
|
+
expect(JSON.stringify(subject)).to.equal('{"a":{"b":[[{"c":true}]]}}');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should add to a Set and ignore the key', function () {
|
|
231
|
+
const values = new Set(['first']);
|
|
232
|
+
new Pathfinder({values}).setVia('values.anything', 'second');
|
|
233
|
+
expect([...values]).to.deep.equal(['first', 'second']);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should write into a Map', function () {
|
|
237
|
+
const map = new Map();
|
|
238
|
+
new Pathfinder({map}).setVia('map.key', 'written');
|
|
239
|
+
expect(map.get('key')).to.equal('written');
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('should reject a WeakSet write explicitly', function () {
|
|
243
|
+
expect(() => new Pathfinder({values: new WeakSet()}).setVia('values.next', {}))
|
|
244
|
+
.to.throw(Error, 'unsupported action for this data type in setValueViaPath');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('should reject unsafe mutation path segments', function () {
|
|
248
|
+
expect(() => new Pathfinder({}).setVia('__proto__.polluted', true)).to.throw(Error);
|
|
249
|
+
expect(() => new Pathfinder({}).setVia('constructor.prototype.polluted', true)).to.throw(Error);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe('wildcard resolution', function () {
|
|
254
|
+
|
|
255
|
+
function wildcardSubject() {
|
|
256
|
+
return {
|
|
257
|
+
a: {
|
|
258
|
+
b: {leaf: 1},
|
|
259
|
+
c: {leaf: 2},
|
|
260
|
+
},
|
|
261
|
+
rows: [{value: 'x'}, {value: 'y'}],
|
|
262
|
+
map: new Map([['first', {leaf: 'm1'}], ['second', {leaf: 'm2'}]]),
|
|
263
|
+
set: new Set(['s1', 's2']),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
it('should return a Map for a trailing wildcard', function () {
|
|
268
|
+
const result = new Pathfinder(wildcardSubject()).getVia('a.*');
|
|
269
|
+
expect(result).to.be.instanceOf(Map);
|
|
270
|
+
expect([...result.keys()]).to.deep.equal(['b', 'c']);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('should resolve a path behind a wildcard', function () {
|
|
274
|
+
const result = new Pathfinder(wildcardSubject()).getVia('a.*.leaf');
|
|
275
|
+
expect(Array.from(result)).to.deep.equal([['b', 1], ['c', 2]]);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('should enumerate array entries behind a wildcard', function () {
|
|
279
|
+
const result = new Pathfinder(wildcardSubject()).getVia('rows.*.value');
|
|
280
|
+
expect(Array.from(result)).to.deep.equal([['0', 'x'], ['1', 'y']]);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('should enumerate Map values behind a wildcard', function () {
|
|
284
|
+
const result = new Pathfinder(wildcardSubject()).getVia('map.*.leaf');
|
|
285
|
+
expect(Array.from(result)).to.deep.equal([['first', 'm1'], ['second', 'm2']]);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('should enumerate Set values behind a wildcard with stringified indices', function () {
|
|
289
|
+
const result = new Pathfinder(wildcardSubject()).getVia('set.*');
|
|
290
|
+
expect(Array.from(result)).to.deep.equal([['0', 's1'], ['1', 's2']]);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should support a leading wildcard', function () {
|
|
294
|
+
const result = new Pathfinder({a: {x: 1}, b: {x: 2}}).getVia('*.x');
|
|
295
|
+
expect(Array.from(result)).to.deep.equal([['a', 1], ['b', 2]]);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('exists() should work through a wildcard', function () {
|
|
299
|
+
expect(new Pathfinder(wildcardSubject()).exists('a.*.leaf')).to.equal(true);
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
describe('deleteVia()', function () {
|
|
304
|
+
|
|
305
|
+
it('should delete an object key', function () {
|
|
306
|
+
const subject = {a: {b: 1, c: 2}};
|
|
307
|
+
new Pathfinder(subject).deleteVia('a.b');
|
|
308
|
+
expect(subject).to.deep.equal({a: {c: 2}});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('should delete a Map entry', function () {
|
|
312
|
+
const map = new Map([['key', 1]]);
|
|
313
|
+
new Pathfinder({map}).deleteVia('map.key');
|
|
314
|
+
expect(map.has('key')).to.equal(false);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('should reject deleting from a Set', function () {
|
|
318
|
+
expect(() => new Pathfinder({values: new Set(['a'])}).deleteVia('values.0'))
|
|
319
|
+
.to.throw(Error, 'unsupported action for this data type in deleteValueViaPath');
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
});
|
|
@@ -557,4 +557,133 @@ describe('Pathfinder', function () {
|
|
|
557
557
|
});
|
|
558
558
|
|
|
559
559
|
});
|
|
560
|
+
|
|
561
|
+
describe('undefined writes keep the key issue #574', function () {
|
|
562
|
+
|
|
563
|
+
// net: setVia() is a write, deleteVia() is the deletion API. Writing
|
|
564
|
+
// undefined must not delete, otherwise a default option declared as
|
|
565
|
+
// undefined would vanish and set-option-from-attribute would stop
|
|
566
|
+
// accepting later attribute changes for it.
|
|
567
|
+
it('setVia() should keep an existing object key when writing undefined', function () {
|
|
568
|
+
const subject = {a: {b: 1}};
|
|
569
|
+
|
|
570
|
+
new Pathfinder(subject).setVia('a.b', undefined);
|
|
571
|
+
|
|
572
|
+
expect(Object.hasOwn(subject.a, 'b')).to.be.true;
|
|
573
|
+
expect(subject.a.b).to.equal(undefined);
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
it('setVia() should create a key when writing undefined to a new path', function () {
|
|
577
|
+
const subject = {};
|
|
578
|
+
|
|
579
|
+
new Pathfinder(subject).setVia('a.b', undefined);
|
|
580
|
+
|
|
581
|
+
expect(Object.hasOwn(subject.a, 'b')).to.be.true;
|
|
582
|
+
expect(subject.a.b).to.equal(undefined);
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
it('exists() should report a key written as undefined', function () {
|
|
586
|
+
const subject = {a: {b: 1}};
|
|
587
|
+
const pathfinder = new Pathfinder(subject);
|
|
588
|
+
|
|
589
|
+
pathfinder.setVia('a.b', undefined);
|
|
590
|
+
|
|
591
|
+
expect(pathfinder.exists('a.b')).to.be.true;
|
|
592
|
+
expect(pathfinder.exists('a.c')).to.be.false;
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
it('deleteVia() should remove a key that was written as undefined', function () {
|
|
596
|
+
const subject = {a: {b: 1, c: 2}};
|
|
597
|
+
const pathfinder = new Pathfinder(subject);
|
|
598
|
+
|
|
599
|
+
pathfinder.setVia('a.b', undefined);
|
|
600
|
+
pathfinder.deleteVia('a.b');
|
|
601
|
+
|
|
602
|
+
expect(Object.hasOwn(subject.a, 'b')).to.be.false;
|
|
603
|
+
expect(subject.a).to.deep.equal({c: 2});
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
it('setVia() should keep an array slot when writing undefined', function () {
|
|
607
|
+
const subject = {a: [1, 2, 3]};
|
|
608
|
+
|
|
609
|
+
new Pathfinder(subject).setVia('a.1', undefined);
|
|
610
|
+
|
|
611
|
+
expect(subject.a.length).to.equal(3);
|
|
612
|
+
expect(Object.hasOwn(subject.a, '1')).to.be.true;
|
|
613
|
+
expect(subject.a[1]).to.equal(undefined);
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
it('deleteVia() should clear an array slot', function () {
|
|
617
|
+
const subject = {a: [1, 2, 3]};
|
|
618
|
+
|
|
619
|
+
new Pathfinder(subject).deleteVia('a.1');
|
|
620
|
+
|
|
621
|
+
expect(subject.a.length).to.equal(3);
|
|
622
|
+
expect(Object.hasOwn(subject.a, '1')).to.be.false;
|
|
623
|
+
});
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
describe('getVia default value issue #576', function () {
|
|
627
|
+
|
|
628
|
+
function subject() {
|
|
629
|
+
return {
|
|
630
|
+
data: {
|
|
631
|
+
calculated: false,
|
|
632
|
+
space: 0,
|
|
633
|
+
label: '',
|
|
634
|
+
button: {},
|
|
635
|
+
list: [1, 2],
|
|
636
|
+
},
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// net: without a second argument the throwing contract is unchanged
|
|
641
|
+
it('should keep throwing past a non traversable value when no default is given', function () {
|
|
642
|
+
expect(() => new Pathfinder(subject()).getVia('data.label.deeper'))
|
|
643
|
+
.to.throw(Error, 'the journey is not at its end');
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
it('should keep resolving a missing leaf to undefined when no default is given', function () {
|
|
647
|
+
expect(new Pathfinder(subject()).getVia('data.missing')).to.equal(undefined);
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
// regression: the second argument was accepted and silently ignored
|
|
651
|
+
it('should return the default instead of throwing past a non traversable value', function () {
|
|
652
|
+
expect(new Pathfinder(subject()).getVia('data.label.deeper', 'fallback')).to.equal('fallback');
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
it('should return the default for a missing mid path segment', function () {
|
|
656
|
+
expect(new Pathfinder(subject()).getVia('data.button.unseeded.width', 0)).to.equal(0);
|
|
657
|
+
expect(new Pathfinder(subject()).getVia('missing.deep.path', 'fallback')).to.equal('fallback');
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
it('should return the default for a missing leaf', function () {
|
|
661
|
+
expect(new Pathfinder(subject()).getVia('data.button.unseeded', 0)).to.equal(0);
|
|
662
|
+
expect(new Pathfinder(subject()).getVia('data.missing', 'fallback')).to.equal('fallback');
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
it('should return the default for a malformed path instead of throwing', function () {
|
|
666
|
+
expect(new Pathfinder(subject()).getVia(undefined, 'fallback')).to.equal('fallback');
|
|
667
|
+
expect(new Pathfinder(subject()).getVia('data.list.1suffix', 'fallback')).to.equal('fallback');
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
it('should return the resolved value and not the default when the path exists', function () {
|
|
671
|
+
expect(new Pathfinder(subject()).getVia('data.calculated', true)).to.equal(false);
|
|
672
|
+
expect(new Pathfinder(subject()).getVia('data.space', 99)).to.equal(0);
|
|
673
|
+
expect(new Pathfinder(subject()).getVia('data.label', 'fallback')).to.equal('');
|
|
674
|
+
expect(new Pathfinder(subject()).getVia('data.list.0', 99)).to.equal(1);
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
it('should return the default for a key whose value is undefined', function () {
|
|
678
|
+
expect(new Pathfinder({a: {b: undefined}}).getVia('a.b', 'fallback')).to.equal('fallback');
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
it('should return the default for a null prototype subject without a match', function () {
|
|
682
|
+
const nullProto = Object.create(null);
|
|
683
|
+
nullProto.leaf = 1;
|
|
684
|
+
|
|
685
|
+
expect(new Pathfinder({nullProto}).getVia('nullProto.leaf', 9)).to.equal(1);
|
|
686
|
+
expect(new Pathfinder({nullProto}).getVia('nullProto.missing', 9)).to.equal(9);
|
|
687
|
+
});
|
|
688
|
+
});
|
|
560
689
|
});
|