@schukai/monster 4.153.0 → 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 +6 -5
- package/source/data/buildtree.mjs +0 -8
- 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 +228 -32
- package/source/data/transformer.mjs +2 -2
- package/source/dom/customcontrol.mjs +35 -9
- package/source/dom/customelement.mjs +293 -31
- package/source/dom/slotted.mjs +46 -31
- package/source/dom/template.mjs +4 -1
- package/source/dom/updater.mjs +57 -17
- package/source/types/base.mjs +7 -6
- package/source/types/basewithoptions.mjs +1 -8
- package/source/types/internal.mjs +4 -10
- package/source/types/node.mjs +1 -3
- package/source/types/observerlist.mjs +9 -17
- package/source/types/proxyobserver.mjs +51 -12
- 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/buildmap.mjs +14 -0
- package/test/cases/data/buildtree.mjs +49 -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 +163 -0
- package/test/cases/data/transformer.mjs +26 -0
- package/test/cases/dom/customcontrol.mjs +85 -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 +408 -0
- package/test/cases/dom/dimension.mjs +34 -10
- package/test/cases/dom/slotted-nodes.mjs +48 -1
- package/test/cases/dom/template.mjs +11 -0
- package/test/cases/dom/updater-remove.mjs +198 -0
- package/test/cases/dom/updater.mjs +118 -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 +189 -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
|
@@ -103,5 +103,107 @@ describe('ObserverList', function () {
|
|
|
103
103
|
});
|
|
104
104
|
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
describe('.detach() removes every occurrence issue #572', function () {
|
|
107
|
+
|
|
108
|
+
// net: behaviour that is already correct and must stay correct
|
|
109
|
+
it('should remove a single attached observer', function () {
|
|
110
|
+
let observerList = new ObserverList()
|
|
111
|
+
let o = new Observer(() => {
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
observerList.attach(o)
|
|
115
|
+
expect(observerList.contains(o)).to.be.true;
|
|
116
|
+
|
|
117
|
+
observerList.detach(o)
|
|
118
|
+
expect(observerList.contains(o)).to.be.false;
|
|
119
|
+
expect(observerList.observers.length).to.equal(0);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should keep other observers when one is detached', function () {
|
|
123
|
+
let observerList = new ObserverList()
|
|
124
|
+
let o1 = new Observer(() => {
|
|
125
|
+
})
|
|
126
|
+
let o2 = new Observer(() => {
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
observerList.attach(o1).attach(o2)
|
|
130
|
+
observerList.detach(o1)
|
|
131
|
+
|
|
132
|
+
expect(observerList.contains(o1)).to.be.false;
|
|
133
|
+
expect(observerList.contains(o2)).to.be.true;
|
|
134
|
+
expect(observerList.observers.length).to.equal(1);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should ignore detaching an observer that was never attached', function () {
|
|
138
|
+
let observerList = new ObserverList()
|
|
139
|
+
let attached = new Observer(() => {
|
|
140
|
+
})
|
|
141
|
+
let foreign = new Observer(() => {
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
observerList.attach(attached)
|
|
145
|
+
observerList.detach(foreign)
|
|
146
|
+
|
|
147
|
+
expect(observerList.contains(attached)).to.be.true;
|
|
148
|
+
expect(observerList.observers.length).to.equal(1);
|
|
149
|
+
});
|
|
107
150
|
|
|
151
|
+
it('should remove every occurrence when the duplicates are not adjacent', function () {
|
|
152
|
+
let observerList = new ObserverList()
|
|
153
|
+
let target = new Observer(() => {
|
|
154
|
+
})
|
|
155
|
+
let other = new Observer(() => {
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
observerList.attach(target).attach(other).attach(target)
|
|
159
|
+
expect(observerList.observers.length).to.equal(3);
|
|
160
|
+
|
|
161
|
+
observerList.detach(target)
|
|
162
|
+
|
|
163
|
+
expect(observerList.contains(target)).to.be.false;
|
|
164
|
+
expect(observerList.contains(other)).to.be.true;
|
|
165
|
+
expect(observerList.observers.length).to.equal(1);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// regression: forward splicing with a cached length skips the entry that
|
|
169
|
+
// moves into the freed slot, so adjacent duplicates survive detaching
|
|
170
|
+
it('should remove a doubly attached observer completely', function () {
|
|
171
|
+
let observerList = new ObserverList()
|
|
172
|
+
let o = new Observer(() => {
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
observerList.attach(o).attach(o)
|
|
176
|
+
expect(observerList.observers.length).to.equal(2);
|
|
177
|
+
|
|
178
|
+
observerList.detach(o)
|
|
179
|
+
|
|
180
|
+
expect(observerList.contains(o)).to.be.false;
|
|
181
|
+
expect(observerList.observers.length).to.equal(0);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should stop notifying a doubly attached observer after detaching', function (done) {
|
|
185
|
+
let observerList = new ObserverList()
|
|
186
|
+
let calls = 0
|
|
187
|
+
let o = new Observer(() => {
|
|
188
|
+
calls++
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
observerList.attach(o).attach(o)
|
|
192
|
+
observerList.detach(o)
|
|
193
|
+
|
|
194
|
+
observerList.notify({}).then(() => {
|
|
195
|
+
setTimeout(() => {
|
|
196
|
+
try {
|
|
197
|
+
expect(calls).to.equal(0);
|
|
198
|
+
done();
|
|
199
|
+
} catch (e) {
|
|
200
|
+
done(e);
|
|
201
|
+
}
|
|
202
|
+
}, 10);
|
|
203
|
+
}).catch(done);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
});
|
|
@@ -241,6 +241,40 @@ describe('ProxyObserver', function () {
|
|
|
241
241
|
proxy.attachObserver(observer);
|
|
242
242
|
delete proxy.getSubject().a;
|
|
243
243
|
});
|
|
244
|
+
|
|
245
|
+
it('should ignore missing and inherited deletes without notifying issue #570', async function () {
|
|
246
|
+
const prototype = {inherited: true};
|
|
247
|
+
const value = Object.create(prototype);
|
|
248
|
+
const proxy = new ProxyObserver(value);
|
|
249
|
+
let notifications = 0;
|
|
250
|
+
proxy.attachObserver(new Observer(function () {
|
|
251
|
+
notifications++;
|
|
252
|
+
}));
|
|
253
|
+
|
|
254
|
+
expect(() => delete proxy.getSubject().missing).to.not.throw();
|
|
255
|
+
expect(() => delete proxy.getSubject().inherited).to.not.throw();
|
|
256
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
257
|
+
|
|
258
|
+
expect(proxy.getSubject().inherited).to.equal(true);
|
|
259
|
+
expect(notifications).to.equal(0);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should report failed non-configurable deletes without notifying issue #570', async function () {
|
|
263
|
+
const value = {};
|
|
264
|
+
Object.defineProperty(value, 'fixed', {
|
|
265
|
+
configurable: false,
|
|
266
|
+
value: true,
|
|
267
|
+
});
|
|
268
|
+
const proxy = new ProxyObserver(value);
|
|
269
|
+
let notifications = 0;
|
|
270
|
+
proxy.attachObserver(new Observer(function () {
|
|
271
|
+
notifications++;
|
|
272
|
+
}));
|
|
273
|
+
|
|
274
|
+
expect(Reflect.deleteProperty(proxy.getSubject(), 'fixed')).to.equal(false);
|
|
275
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
276
|
+
expect(notifications).to.equal(0);
|
|
277
|
+
});
|
|
244
278
|
});
|
|
245
279
|
|
|
246
280
|
describe('symbol properties', function () {
|
|
@@ -342,6 +376,161 @@ describe('ProxyObserver', function () {
|
|
|
342
376
|
expect(proxy.getSubject().map).to.equal(replacement);
|
|
343
377
|
expect(notifications).to.equal(1);
|
|
344
378
|
});
|
|
379
|
+
|
|
380
|
+
it('should obey frozen-property proxy invariants issue #570', function () {
|
|
381
|
+
const nested = {value: true};
|
|
382
|
+
const frozen = Object.freeze({nested});
|
|
383
|
+
const subject = new ProxyObserver({frozen}).getSubject();
|
|
384
|
+
|
|
385
|
+
expect(() => subject.frozen.nested).to.not.throw();
|
|
386
|
+
expect(subject.frozen.nested).to.equal(nested);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('should preserve confirmed internal-slot native objects issue #570', async function () {
|
|
390
|
+
const weakKey = {};
|
|
391
|
+
const regexp = /monster/g;
|
|
392
|
+
const buffer = new ArrayBuffer(8);
|
|
393
|
+
const view = new DataView(buffer);
|
|
394
|
+
const typed = new Uint8Array(buffer);
|
|
395
|
+
const weakMap = new WeakMap([[weakKey, 'value']]);
|
|
396
|
+
const weakSet = new WeakSet([weakKey]);
|
|
397
|
+
const promise = Promise.resolve('resolved');
|
|
398
|
+
const subject = new ProxyObserver({
|
|
399
|
+
regexp,
|
|
400
|
+
buffer,
|
|
401
|
+
view,
|
|
402
|
+
typed,
|
|
403
|
+
weakMap,
|
|
404
|
+
weakSet,
|
|
405
|
+
promise,
|
|
406
|
+
}).getSubject();
|
|
407
|
+
|
|
408
|
+
expect(subject.regexp).to.equal(regexp);
|
|
409
|
+
expect(subject.regexp.test('monster')).to.equal(true);
|
|
410
|
+
expect(subject.buffer).to.equal(buffer);
|
|
411
|
+
expect(subject.buffer.byteLength).to.equal(8);
|
|
412
|
+
expect(subject.view).to.equal(view);
|
|
413
|
+
expect(subject.view.byteLength).to.equal(8);
|
|
414
|
+
expect(subject.typed).to.equal(typed);
|
|
415
|
+
expect(subject.typed.length).to.equal(8);
|
|
416
|
+
expect(subject.weakMap.get(weakKey)).to.equal('value');
|
|
417
|
+
expect(subject.weakSet.has(weakKey)).to.equal(true);
|
|
418
|
+
expect(await subject.promise).to.equal('resolved');
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
describe('numeric identity issue #571', function () {
|
|
423
|
+
|
|
424
|
+
// net: assigning an unchanged primitive must not notify, a changed one must
|
|
425
|
+
it('should not notify when an identical number is assigned', async function () {
|
|
426
|
+
const proxy = new ProxyObserver({a: 1});
|
|
427
|
+
let notifications = 0;
|
|
428
|
+
proxy.attachObserver(new Observer(function () {
|
|
429
|
+
notifications++;
|
|
430
|
+
}));
|
|
431
|
+
|
|
432
|
+
proxy.getSubject().a = 1;
|
|
433
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
434
|
+
|
|
435
|
+
expect(notifications).to.equal(0);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it('should notify once when a number actually changes', async function () {
|
|
439
|
+
const proxy = new ProxyObserver({a: 1});
|
|
440
|
+
let notifications = 0;
|
|
441
|
+
proxy.attachObserver(new Observer(function () {
|
|
442
|
+
notifications++;
|
|
443
|
+
}));
|
|
444
|
+
|
|
445
|
+
proxy.getSubject().a = 2;
|
|
446
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
447
|
+
|
|
448
|
+
expect(notifications).to.equal(1);
|
|
449
|
+
expect(proxy.getRealSubject().a).to.equal(2);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it('should keep notifying when NaN is replaced by a number', async function () {
|
|
453
|
+
const proxy = new ProxyObserver({a: NaN});
|
|
454
|
+
let notifications = 0;
|
|
455
|
+
proxy.attachObserver(new Observer(function () {
|
|
456
|
+
notifications++;
|
|
457
|
+
}));
|
|
458
|
+
|
|
459
|
+
proxy.getSubject().a = 5;
|
|
460
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
461
|
+
|
|
462
|
+
expect(notifications).to.equal(1);
|
|
463
|
+
expect(proxy.getRealSubject().a).to.equal(5);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it('should keep notifying when a number is replaced by NaN', async function () {
|
|
467
|
+
const proxy = new ProxyObserver({a: 5});
|
|
468
|
+
let notifications = 0;
|
|
469
|
+
proxy.attachObserver(new Observer(function () {
|
|
470
|
+
notifications++;
|
|
471
|
+
}));
|
|
472
|
+
|
|
473
|
+
proxy.getSubject().a = NaN;
|
|
474
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
475
|
+
|
|
476
|
+
expect(notifications).to.equal(1);
|
|
477
|
+
expect(Number.isNaN(proxy.getRealSubject().a)).to.be.true;
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// regression: an identical NaN is not a change
|
|
481
|
+
it('should not notify when an identical NaN is assigned', async function () {
|
|
482
|
+
const proxy = new ProxyObserver({a: NaN});
|
|
483
|
+
let notifications = 0;
|
|
484
|
+
proxy.attachObserver(new Observer(function () {
|
|
485
|
+
notifications++;
|
|
486
|
+
}));
|
|
487
|
+
|
|
488
|
+
proxy.getSubject().a = NaN;
|
|
489
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
490
|
+
|
|
491
|
+
expect(notifications).to.equal(0);
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it('should not notify when an identical NaN is assigned on a nested path', async function () {
|
|
495
|
+
const proxy = new ProxyObserver({options: {size: NaN}});
|
|
496
|
+
let notifications = 0;
|
|
497
|
+
proxy.attachObserver(new Observer(function () {
|
|
498
|
+
notifications++;
|
|
499
|
+
}));
|
|
500
|
+
|
|
501
|
+
proxy.getSubject().options.size = NaN;
|
|
502
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
503
|
+
|
|
504
|
+
expect(notifications).to.equal(0);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it('should not notify when an identical NaN is assigned to an array entry', async function () {
|
|
508
|
+
const proxy = new ProxyObserver({values: [NaN, 1]});
|
|
509
|
+
let notifications = 0;
|
|
510
|
+
proxy.attachObserver(new Observer(function () {
|
|
511
|
+
notifications++;
|
|
512
|
+
}));
|
|
513
|
+
|
|
514
|
+
proxy.getSubject().values[0] = NaN;
|
|
515
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
516
|
+
|
|
517
|
+
expect(notifications).to.equal(0);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it('should not notify repeatedly when the same NaN is written many times', async function () {
|
|
521
|
+
const proxy = new ProxyObserver({a: NaN});
|
|
522
|
+
let notifications = 0;
|
|
523
|
+
proxy.attachObserver(new Observer(function () {
|
|
524
|
+
notifications++;
|
|
525
|
+
}));
|
|
526
|
+
|
|
527
|
+
for (let i = 0; i < 5; i++) {
|
|
528
|
+
proxy.getSubject().a = NaN;
|
|
529
|
+
}
|
|
530
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
531
|
+
|
|
532
|
+
expect(notifications).to.equal(0);
|
|
533
|
+
});
|
|
345
534
|
});
|
|
346
535
|
|
|
347
536
|
})
|
|
@@ -8,6 +8,10 @@ describe('escapeString', function () {
|
|
|
8
8
|
|
|
9
9
|
before(function (done) {
|
|
10
10
|
|
|
11
|
+
// A cold ESM load of the library plus the jsdom bootstrap can exceed the
|
|
12
|
+
// 2000ms default, which made this hook fail depending on file order.
|
|
13
|
+
this.timeout(20000);
|
|
14
|
+
|
|
11
15
|
let promises = []
|
|
12
16
|
promises.push(initJSDOM());
|
|
13
17
|
if(!globalThis['crypto']) {
|
|
@@ -257,5 +257,68 @@ describe('TokenList', function () {
|
|
|
257
257
|
});
|
|
258
258
|
|
|
259
259
|
|
|
260
|
-
|
|
260
|
+
describe('contains all tokens issue #572', function () {
|
|
261
|
+
|
|
262
|
+
// net: behaviour that is already correct and must stay correct
|
|
263
|
+
it('should contain a multi token string when every token is present', function () {
|
|
264
|
+
let list = new TokenList("start middle end");
|
|
265
|
+
expect(list.contains('start middle')).to.be.true;
|
|
266
|
+
expect(list.contains('start middle end')).to.be.true;
|
|
267
|
+
expect(list.contains('end start')).to.be.true;
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should ignore surrounding and repeated whitespace', function () {
|
|
271
|
+
let list = new TokenList("start middle end");
|
|
272
|
+
expect(list.contains(' start middle ')).to.be.true;
|
|
273
|
+
expect(list.contains(' end ')).to.be.true;
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should not contain an empty or whitespace only value', function () {
|
|
277
|
+
let list = new TokenList("start middle end");
|
|
278
|
+
expect(list.contains('')).to.be.false;
|
|
279
|
+
expect(list.contains(' ')).to.be.false;
|
|
280
|
+
expect(list.contains([])).to.be.false;
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('should keep the iterable branch requiring every token', function () {
|
|
284
|
+
let list = new TokenList("start middle end");
|
|
285
|
+
expect(list.contains(['start', 'middle'])).to.be.true;
|
|
286
|
+
expect(list.contains(['start', 'xyz'])).to.be.false;
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// regression: a multi token string needs every token, not just one
|
|
290
|
+
it('should not contain a multi token string when one token is missing', function () {
|
|
291
|
+
let list = new TokenList("start middle end");
|
|
292
|
+
expect(list.contains('start xyz')).to.be.false;
|
|
293
|
+
});
|
|
261
294
|
|
|
295
|
+
it('should not contain a multi token string when the missing token comes first', function () {
|
|
296
|
+
let list = new TokenList("start middle end");
|
|
297
|
+
expect(list.contains('xyz start')).to.be.false;
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('should not contain a multi token string when only two of three tokens match', function () {
|
|
301
|
+
let list = new TokenList("start middle end");
|
|
302
|
+
expect(list.contains('start middle xyz')).to.be.false;
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('should answer identically for a string and the equivalent array', function () {
|
|
306
|
+
let list = new TokenList("start middle end");
|
|
307
|
+
let cases = [
|
|
308
|
+
['start middle', ['start', 'middle']],
|
|
309
|
+
['start xyz', ['start', 'xyz']],
|
|
310
|
+
['xyz abc', ['xyz', 'abc']],
|
|
311
|
+
['end', ['end']],
|
|
312
|
+
];
|
|
313
|
+
|
|
314
|
+
for (let [asString, asArray] of cases) {
|
|
315
|
+
expect(list.contains(asString)).to.equal(
|
|
316
|
+
list.contains(asArray),
|
|
317
|
+
'mismatch for ' + JSON.stringify(asString),
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
});
|
|
@@ -210,4 +210,119 @@ describe('Clone', function () {
|
|
|
210
210
|
|
|
211
211
|
});
|
|
212
212
|
|
|
213
|
+
describe('cyclic graphs and constructor free copies issue #573', function () {
|
|
214
|
+
|
|
215
|
+
class NeedsArgument {
|
|
216
|
+
constructor(required) {
|
|
217
|
+
if (required === undefined) {
|
|
218
|
+
throw new Error("required argument missing");
|
|
219
|
+
}
|
|
220
|
+
this.required = required;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
describe() {
|
|
224
|
+
return `value:${this.required}`;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// net: behaviour that is already correct and must stay correct
|
|
229
|
+
it('should keep cloning plain nested structures deeply', function () {
|
|
230
|
+
let source = {a: {b: {c: 1}}, list: [1, {d: 2}]};
|
|
231
|
+
let result = clone(source);
|
|
232
|
+
|
|
233
|
+
expect(result).to.deep.equal(source);
|
|
234
|
+
expect(result.a).not.to.equal(source.a);
|
|
235
|
+
expect(result.list[1]).not.to.equal(source.list[1]);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should keep the prototype and instanceof of a cloned class instance', function () {
|
|
239
|
+
let source = new A({x: 1});
|
|
240
|
+
let result = clone(source);
|
|
241
|
+
|
|
242
|
+
expect(result).to.be.instanceOf(A);
|
|
243
|
+
expect(Object.getPrototypeOf(result)).to.equal(Object.getPrototypeOf(source));
|
|
244
|
+
expect(typeof result.a).to.equal('function');
|
|
245
|
+
expect(result.b).to.deep.equal({x: 1});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should keep honoring getClone()', function () {
|
|
249
|
+
let result = clone(new B({x: 1}));
|
|
250
|
+
expect(result).to.equal('DONE');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// regression: a cyclic graph must terminate instead of exhausting the stack
|
|
254
|
+
it('should clone a self referencing object', function () {
|
|
255
|
+
let source = {name: 'root'};
|
|
256
|
+
source.self = source;
|
|
257
|
+
|
|
258
|
+
let result = clone(source);
|
|
259
|
+
|
|
260
|
+
expect(result).not.to.equal(source);
|
|
261
|
+
expect(result.name).to.equal('root');
|
|
262
|
+
expect(result.self).to.equal(result);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('should clone a cycle that runs through an array', function () {
|
|
266
|
+
let source = {items: []};
|
|
267
|
+
source.items.push(source);
|
|
268
|
+
|
|
269
|
+
let result = clone(source);
|
|
270
|
+
|
|
271
|
+
expect(result.items[0]).to.equal(result);
|
|
272
|
+
expect(result.items).not.to.equal(source.items);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should clone a mutual reference between two objects', function () {
|
|
276
|
+
let first = {name: 'first'};
|
|
277
|
+
let second = {name: 'second', first};
|
|
278
|
+
first.second = second;
|
|
279
|
+
|
|
280
|
+
let result = clone(first);
|
|
281
|
+
|
|
282
|
+
expect(result.second.first).to.equal(result);
|
|
283
|
+
expect(result.second).not.to.equal(second);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('should preserve a shared reference instead of duplicating it', function () {
|
|
287
|
+
let shared = {value: 1};
|
|
288
|
+
let source = {left: shared, right: shared};
|
|
289
|
+
|
|
290
|
+
let result = clone(source);
|
|
291
|
+
|
|
292
|
+
expect(result.left).to.equal(result.right);
|
|
293
|
+
expect(result.left).not.to.equal(shared);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('should keep honoring getClone() for an object reached inside a cycle', function () {
|
|
297
|
+
let source = {inner: new B({x: 1})};
|
|
298
|
+
source.self = source;
|
|
299
|
+
|
|
300
|
+
let result = clone(source);
|
|
301
|
+
|
|
302
|
+
expect(result.inner).to.equal('DONE');
|
|
303
|
+
expect(result.self).to.equal(result);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// regression: copying must not run the constructor of the source class
|
|
307
|
+
it('should clone an instance whose constructor requires an argument', function () {
|
|
308
|
+
let source = new NeedsArgument('payload');
|
|
309
|
+
|
|
310
|
+
let result = clone(source);
|
|
311
|
+
|
|
312
|
+
expect(result).to.be.instanceOf(NeedsArgument);
|
|
313
|
+
expect(result).not.to.equal(source);
|
|
314
|
+
expect(result.required).to.equal('payload');
|
|
315
|
+
expect(result.describe()).to.equal('value:payload');
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('should clone a nested instance whose constructor requires an argument', function () {
|
|
319
|
+
let source = {level: {instance: new NeedsArgument(7)}};
|
|
320
|
+
|
|
321
|
+
let result = clone(source);
|
|
322
|
+
|
|
323
|
+
expect(result.level.instance).to.be.instanceOf(NeedsArgument);
|
|
324
|
+
expect(result.level.instance.required).to.equal(7);
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
213
328
|
});
|