@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.
Files changed (52) hide show
  1. package/package.json +1 -1
  2. package/source/components/form/message-state-button.mjs +1 -1
  3. package/source/components/form/select-link.mjs +12 -1
  4. package/source/components/state/log.mjs +3 -7
  5. package/source/components/state/thread.mjs +3 -7
  6. package/source/components/time/day.mjs +0 -19
  7. package/source/components/time/month-calendar.mjs +5 -1
  8. package/source/data/buildmap.mjs +6 -5
  9. package/source/data/buildtree.mjs +0 -8
  10. package/source/data/datasource.mjs +3 -10
  11. package/source/data/diff.mjs +13 -12
  12. package/source/data/extend.mjs +23 -1
  13. package/source/data/pathfinder.mjs +228 -32
  14. package/source/data/transformer.mjs +2 -2
  15. package/source/dom/customcontrol.mjs +35 -9
  16. package/source/dom/customelement.mjs +293 -31
  17. package/source/dom/slotted.mjs +46 -31
  18. package/source/dom/template.mjs +4 -1
  19. package/source/dom/updater.mjs +57 -17
  20. package/source/types/base.mjs +7 -6
  21. package/source/types/basewithoptions.mjs +1 -8
  22. package/source/types/internal.mjs +4 -10
  23. package/source/types/node.mjs +1 -3
  24. package/source/types/observerlist.mjs +9 -17
  25. package/source/types/proxyobserver.mjs +51 -12
  26. package/source/types/tokenlist.mjs +13 -8
  27. package/source/util/clone.mjs +44 -14
  28. package/test/cases/browser-bundle.mjs +75 -0
  29. package/test/cases/components/time/month-calendar.mjs +115 -0
  30. package/test/cases/data/buildmap.mjs +14 -0
  31. package/test/cases/data/buildtree.mjs +49 -0
  32. package/test/cases/data/diff.mjs +86 -0
  33. package/test/cases/data/extend.mjs +52 -0
  34. package/test/cases/data/pathfinder-resolution-baseline.mjs +322 -0
  35. package/test/cases/data/pathfinder.mjs +163 -0
  36. package/test/cases/data/transformer.mjs +26 -0
  37. package/test/cases/dom/customcontrol.mjs +85 -0
  38. package/test/cases/dom/customelement-rebind-baseline.mjs +323 -0
  39. package/test/cases/dom/customelement-teardown.mjs +408 -0
  40. package/test/cases/dom/customelement.mjs +408 -0
  41. package/test/cases/dom/dimension.mjs +34 -10
  42. package/test/cases/dom/slotted-nodes.mjs +48 -1
  43. package/test/cases/dom/template.mjs +11 -0
  44. package/test/cases/dom/updater-remove.mjs +198 -0
  45. package/test/cases/dom/updater.mjs +118 -0
  46. package/test/cases/packaging.mjs +213 -0
  47. package/test/cases/types/base.mjs +50 -0
  48. package/test/cases/types/observerlist.mjs +103 -1
  49. package/test/cases/types/proxyobserver.mjs +189 -0
  50. package/test/cases/types/regex.mjs +4 -0
  51. package/test/cases/types/tokenlist.mjs +64 -1
  52. 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
  });