@schukai/monster 4.148.1 → 4.148.3
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/datatable/filter/date-presets.mjs +13 -0
- package/source/components/datatable/filter/date-range.mjs +12 -0
- package/source/components/datatable/filter/date-time.mjs +14 -1
- package/source/components/datatable/filter/date.mjs +14 -1
- package/source/components/datatable/filter/input.mjs +14 -1
- package/source/components/datatable/filter/range.mjs +11 -0
- package/source/components/datatable/filter/text-operator.mjs +19 -0
- package/source/components/datatable/filter/time.mjs +14 -1
- package/source/components/form/buy-box.mjs +2 -0
- package/source/components/form/cart-control.mjs +2 -0
- package/source/components/state/state.mjs +2 -0
- package/source/data/extend.mjs +7 -1
- package/source/data/pathfinder.mjs +44 -6
- package/source/dom/customcontrol.mjs +28 -23
- package/source/dom/customelement.mjs +105 -39
- package/source/dom/updater.mjs +76 -25
- package/source/dom/util/extract-keys.mjs +2 -1
- package/source/dom/util/init-options-from-attributes.mjs +1 -1
- package/source/dom/util/set-option-from-attribute.mjs +14 -3
- package/source/types/proxyobserver.mjs +15 -0
- package/source/util/clone.mjs +8 -6
- package/test/cases/components/datatable/filter-form-value.mjs +98 -0
- package/test/cases/components/form/non-value-form-association.mjs +53 -0
- package/test/cases/data/extend.mjs +27 -1
- package/test/cases/data/pathfinder.mjs +105 -1
- package/test/cases/dom/customcontrol.mjs +188 -6
- package/test/cases/dom/customelement-initfromscripthost.mjs +62 -0
- package/test/cases/dom/customelement.mjs +123 -2
- package/test/cases/dom/updater.mjs +293 -0
- package/test/cases/dom/util/extract-keys.mjs +14 -0
- package/test/cases/dom/util/init-options-from-attributes.mjs +18 -0
- package/test/cases/dom/util/set-option-from-attribute.mjs +151 -0
- package/test/cases/types/proxyobserver.mjs +52 -0
- package/test/cases/util/clone.mjs +24 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { expect } from 'chai';
|
|
4
|
+
import { initJSDOM } from '../../../util/jsdom.mjs';
|
|
5
|
+
|
|
6
|
+
describe('non-value component form association issue #503', function () {
|
|
7
|
+
let CustomControl;
|
|
8
|
+
let components;
|
|
9
|
+
|
|
10
|
+
before(async function () {
|
|
11
|
+
await initJSDOM({});
|
|
12
|
+
await import('element-internals-polyfill');
|
|
13
|
+
({ CustomControl } = await import('../../../../source/dom/customcontrol.mjs'));
|
|
14
|
+
const [{ State }, { CartControl }, { BuyBox }] = await Promise.all([
|
|
15
|
+
import('../../../../source/components/state/state.mjs'),
|
|
16
|
+
import('../../../../source/components/form/cart-control.mjs'),
|
|
17
|
+
import('../../../../source/components/form/buy-box.mjs'),
|
|
18
|
+
]);
|
|
19
|
+
components = [State, CartControl, BuyBox];
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(function () {
|
|
23
|
+
document.getElementById('mocks').innerHTML = '';
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should opt non-value components out of form association', function () {
|
|
27
|
+
for (const Component of components) {
|
|
28
|
+
expect(Component.formAssociated, Component.getTag()).to.equal(false);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should preserve the public CustomControl inheritance', function () {
|
|
33
|
+
for (const Component of components) {
|
|
34
|
+
const control = document.createElement(Component.getTag());
|
|
35
|
+
expect(control, Component.getTag()).to.be.instanceof(CustomControl);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should not contribute values or throw during form reset', function () {
|
|
40
|
+
for (const Component of components) {
|
|
41
|
+
const form = document.createElement('form');
|
|
42
|
+
const control = document.createElement(Component.getTag());
|
|
43
|
+
control.setAttribute('name', Component.getTag());
|
|
44
|
+
form.appendChild(control);
|
|
45
|
+
document.getElementById('mocks').appendChild(form);
|
|
46
|
+
|
|
47
|
+
expect(() => form.reset()).not.to.throw();
|
|
48
|
+
expect(new window.FormData(form).get(Component.getTag())).to.equal(null);
|
|
49
|
+
|
|
50
|
+
form.remove();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -156,7 +156,33 @@ describe('extend function', () => {
|
|
|
156
156
|
const result = extend(target, source);
|
|
157
157
|
expect(result).to.deep.equal({ a: [3, 4] });
|
|
158
158
|
});
|
|
159
|
-
});
|
|
160
159
|
|
|
160
|
+
it('should ignore inherited enumerable properties issue #499', () => {
|
|
161
|
+
const prototype = { inherited: 'unsafe' };
|
|
162
|
+
const source = Object.assign(Object.create(prototype), { own: 'safe' });
|
|
163
|
+
|
|
164
|
+
expect(extend({}, source)).to.deep.equal({ own: 'safe' });
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
[
|
|
168
|
+
'{"__proto__":{"monsterExtendPolluted":"yes"}}',
|
|
169
|
+
'{"nested":{"__proto__":{"monsterExtendPolluted":"yes"}}}',
|
|
170
|
+
'{"constructor":{"prototype":{"monsterExtendPolluted":"yes"}}}',
|
|
171
|
+
].forEach((json) => {
|
|
172
|
+
it(`should not mutate Object.prototype for ${json} issue #499`, () => {
|
|
173
|
+
delete Object.prototype.monsterExtendPolluted;
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const result = extend({}, JSON.parse(json));
|
|
177
|
+
|
|
178
|
+
expect(Object.prototype.monsterExtendPolluted).to.equal(undefined);
|
|
179
|
+
expect(({}).monsterExtendPolluted).to.equal(undefined);
|
|
180
|
+
expect(result).to.be.an('object');
|
|
181
|
+
} finally {
|
|
182
|
+
delete Object.prototype.monsterExtendPolluted;
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
});
|
|
161
187
|
|
|
162
188
|
|
|
@@ -5,6 +5,111 @@ import {Pathfinder} from "../../../source/data/pathfinder.mjs";
|
|
|
5
5
|
|
|
6
6
|
describe('Pathfinder', function () {
|
|
7
7
|
|
|
8
|
+
describe('mutation path security issue #492', function () {
|
|
9
|
+
const probe = 'monsterPathfinderSecurityProbe';
|
|
10
|
+
|
|
11
|
+
afterEach(function () {
|
|
12
|
+
delete Object.prototype[probe];
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
[
|
|
16
|
+
`__proto__.${probe}`,
|
|
17
|
+
`constructor.prototype.${probe}`,
|
|
18
|
+
['__proto__', probe],
|
|
19
|
+
['constructor', 'prototype', probe],
|
|
20
|
+
].forEach(function (path) {
|
|
21
|
+
it(`setVia(${JSON.stringify(path)}) should reject unsafe mutation paths`, function () {
|
|
22
|
+
try {
|
|
23
|
+
expect(() => new Pathfinder({}).setVia(path, 'polluted')).to.throw(Error);
|
|
24
|
+
expect(Object.prototype).to.not.have.own.property(probe);
|
|
25
|
+
} finally {
|
|
26
|
+
delete Object.prototype[probe];
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('deleteVia() should not cross a prototype boundary', function () {
|
|
32
|
+
Object.defineProperty(Object.prototype, probe, {
|
|
33
|
+
configurable: true,
|
|
34
|
+
value: 'preserved',
|
|
35
|
+
writable: true,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
expect(() => new Pathfinder({}).deleteVia(`__proto__.${probe}`)).to.throw(Error);
|
|
40
|
+
expect(Object.prototype[probe]).to.equal('preserved');
|
|
41
|
+
} finally {
|
|
42
|
+
delete Object.prototype[probe];
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('setVia() should keep safe paths working', function () {
|
|
47
|
+
const subject = {};
|
|
48
|
+
new Pathfinder(subject).setVia('safe.nested.value', true);
|
|
49
|
+
expect(subject).to.deep.equal({safe: {nested: {value: true}}});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('array and collection path contracts issue #493', function () {
|
|
54
|
+
it('getVia() should accept an array path without mutating it', function () {
|
|
55
|
+
const path = ['a', 'b'];
|
|
56
|
+
const subject = {a: {b: 4}};
|
|
57
|
+
|
|
58
|
+
expect(new Pathfinder(subject).getVia(path)).to.equal(4);
|
|
59
|
+
expect(path).to.deep.equal(['a', 'b']);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('getVia([]) should return the root subject', function () {
|
|
63
|
+
const subject = {a: true};
|
|
64
|
+
expect(new Pathfinder(subject).getVia([])).to.equal(subject);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('setVia() should not mutate an array path', function () {
|
|
68
|
+
const path = ['a', 'b'];
|
|
69
|
+
const subject = {};
|
|
70
|
+
|
|
71
|
+
new Pathfinder(subject).setVia(path, 4);
|
|
72
|
+
|
|
73
|
+
expect(subject).to.deep.equal({a: {b: 4}});
|
|
74
|
+
expect(path).to.deep.equal(['a', 'b']);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('deleteVia() should not mutate an array path', function () {
|
|
78
|
+
const path = ['a', 'b'];
|
|
79
|
+
const subject = {a: {b: 4, c: 5}};
|
|
80
|
+
|
|
81
|
+
new Pathfinder(subject).deleteVia(path);
|
|
82
|
+
|
|
83
|
+
expect(subject).to.deep.equal({a: {c: 5}});
|
|
84
|
+
expect(path).to.deep.equal(['a', 'b']);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('empty mutation paths should be no-ops', function () {
|
|
88
|
+
const setPath = [];
|
|
89
|
+
const deletePath = [];
|
|
90
|
+
const subject = {a: true};
|
|
91
|
+
const pathfinder = new Pathfinder(subject);
|
|
92
|
+
|
|
93
|
+
pathfinder.setVia(setPath, false).deleteVia(deletePath);
|
|
94
|
+
|
|
95
|
+
expect(subject).to.deep.equal({a: true});
|
|
96
|
+
expect(setPath).to.deep.equal([]);
|
|
97
|
+
expect(deletePath).to.deep.equal([]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('setVia() should add values to a Set', function () {
|
|
101
|
+
const values = new Set(['first']);
|
|
102
|
+
new Pathfinder({values}).setVia('values.next', 'second');
|
|
103
|
+
expect([...values]).to.deep.equal(['first', 'second']);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('setVia() should reject WeakSet writes explicitly', function () {
|
|
107
|
+
const values = new WeakSet();
|
|
108
|
+
expect(() => new Pathfinder({values}).setVia('values.next', {}))
|
|
109
|
+
.to.throw(Error, 'unsupported action for this data type in setValueViaPath');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
8
113
|
let convertMapResult = function (r) {
|
|
9
114
|
if (r instanceof Map) {
|
|
10
115
|
r = Object.fromEntries(r);
|
|
@@ -419,4 +524,3 @@ describe('Pathfinder', function () {
|
|
|
419
524
|
|
|
420
525
|
});
|
|
421
526
|
});
|
|
422
|
-
|
|
@@ -88,14 +88,20 @@ describe('DOM', function () {
|
|
|
88
88
|
|
|
89
89
|
})
|
|
90
90
|
|
|
91
|
-
it('should
|
|
91
|
+
it('should not reflect computed disabledness to the host attribute issue #500', function () {
|
|
92
92
|
expect(element.formDisabledCallback()).to.be.undefined;
|
|
93
93
|
expect(element.hasAttribute('disabled')).to.be.false;
|
|
94
94
|
expect(element.formDisabledCallback(true)).to.be.undefined;
|
|
95
|
-
expect(element.hasAttribute('disabled')).to.be.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
expect(element.hasAttribute('disabled')).to.be.false;
|
|
96
|
+
expect(element.getOption('disabled')).to.be.false;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should preserve an author-provided form attribute issue #500', function () {
|
|
100
|
+
element.setAttribute('form', 'author-form');
|
|
101
|
+
|
|
102
|
+
element.formAssociatedCallback(null);
|
|
103
|
+
|
|
104
|
+
expect(element.getAttribute('form')).to.equal('author-form');
|
|
99
105
|
});
|
|
100
106
|
|
|
101
107
|
})
|
|
@@ -319,9 +325,185 @@ describe('DOM', function () {
|
|
|
319
325
|
expect(d.value).to.equal('seed');
|
|
320
326
|
});
|
|
321
327
|
|
|
328
|
+
it('formStateRestoreCallback() should restore through the value setter issue #501', function () {
|
|
329
|
+
const htmlTAG = 'monster-customcontrol-state-restore';
|
|
330
|
+
|
|
331
|
+
class StateRestoreComponent extends CustomControl {
|
|
332
|
+
static getTag() {
|
|
333
|
+
return htmlTAG;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
get value() {
|
|
337
|
+
return this._value ?? '';
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
set value(value) {
|
|
341
|
+
this._value = value;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
registerCustomElement(StateRestoreComponent);
|
|
346
|
+
const control = document.createElement(htmlTAG);
|
|
347
|
+
control.value = 'changed';
|
|
348
|
+
|
|
349
|
+
control.formStateRestoreCallback('restored', 'restore');
|
|
350
|
+
|
|
351
|
+
expect(control.value).to.equal('restored');
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('should normalize a removed value attribute to an empty string issue #501', async function () {
|
|
355
|
+
const htmlTAG = 'monster-customcontrol-value-removal';
|
|
356
|
+
|
|
357
|
+
class ValueRemovalComponent extends CustomControl {
|
|
358
|
+
static getTag() {
|
|
359
|
+
return htmlTAG;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
get defaults() {
|
|
363
|
+
return Object.assign({}, super.defaults, {
|
|
364
|
+
shadowMode: false,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
get value() {
|
|
369
|
+
return this._value ?? '';
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
set value(value) {
|
|
373
|
+
this._value = value;
|
|
374
|
+
this.lastAssignedValue = value;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
registerCustomElement(ValueRemovalComponent);
|
|
379
|
+
const control = document.createElement(htmlTAG);
|
|
380
|
+
document.body.appendChild(control);
|
|
381
|
+
control.setAttribute('value', 'seed');
|
|
382
|
+
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
383
|
+
control.removeAttribute('value');
|
|
384
|
+
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
385
|
+
|
|
386
|
+
expect(control.lastAssignedValue).to.equal('');
|
|
387
|
+
expect(control.value).to.equal('');
|
|
388
|
+
control.remove();
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('should replay a pre-upgrade value once after assembly issue #501', async function () {
|
|
392
|
+
const htmlTAG = 'monster-customcontrol-pre-upgrade';
|
|
393
|
+
const control = document.createElement(htmlTAG);
|
|
394
|
+
control.value = 'assigned-before-definition';
|
|
395
|
+
document.body.appendChild(control);
|
|
396
|
+
|
|
397
|
+
class PreUpgradeComponent extends CustomControl {
|
|
398
|
+
static getTag() {
|
|
399
|
+
return htmlTAG;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
get defaults() {
|
|
403
|
+
return Object.assign({}, super.defaults, {
|
|
404
|
+
shadowMode: false,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
get value() {
|
|
409
|
+
return this._value ?? '';
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
set value(value) {
|
|
413
|
+
this._value = value;
|
|
414
|
+
this.setterCalls = (this.setterCalls ?? 0) + 1;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
registerCustomElement(PreUpgradeComponent);
|
|
419
|
+
await customElements.whenDefined(htmlTAG);
|
|
420
|
+
await Promise.resolve();
|
|
421
|
+
|
|
422
|
+
expect(control).to.be.instanceof(PreUpgradeComponent);
|
|
423
|
+
expect(Object.prototype.hasOwnProperty.call(control, 'value')).to.be.false;
|
|
424
|
+
expect(control.value).to.equal('assigned-before-definition');
|
|
425
|
+
expect(control.setterCalls).to.equal(1);
|
|
426
|
+
|
|
427
|
+
control.remove();
|
|
428
|
+
document.body.appendChild(control);
|
|
429
|
+
await Promise.resolve();
|
|
430
|
+
expect(control.setterCalls).to.equal(1);
|
|
431
|
+
control.remove();
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it('should preserve native fieldset and explicit form lifecycle issue #500', async function () {
|
|
435
|
+
if (window.navigator.userAgent.includes('jsdom')) {
|
|
436
|
+
this.skip();
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const htmlTAG = 'monster-customcontrol-native-lifecycle';
|
|
440
|
+
|
|
441
|
+
class NativeLifecycleComponent extends CustomControl {
|
|
442
|
+
static getTag() {
|
|
443
|
+
return htmlTAG;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
get defaults() {
|
|
447
|
+
return Object.assign({}, super.defaults, {
|
|
448
|
+
shadowMode: false,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
get value() {
|
|
453
|
+
return this._value ?? '';
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
set value(value) {
|
|
457
|
+
this._value = value ?? '';
|
|
458
|
+
this.setFormValue(this._value);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
registerCustomElement(NativeLifecycleComponent);
|
|
463
|
+
|
|
464
|
+
const externalForm = document.createElement('form');
|
|
465
|
+
externalForm.id = 'external-form-500';
|
|
466
|
+
document.body.appendChild(externalForm);
|
|
467
|
+
|
|
468
|
+
const externalControl = document.createElement(htmlTAG);
|
|
469
|
+
externalControl.setAttribute('name', 'external');
|
|
470
|
+
externalControl.setAttribute('form', externalForm.id);
|
|
471
|
+
document.body.appendChild(externalControl);
|
|
472
|
+
externalControl.value = 'submitted';
|
|
473
|
+
|
|
474
|
+
expect(new FormData(externalForm).get('external')).to.equal('submitted');
|
|
475
|
+
externalControl.remove();
|
|
476
|
+
expect(externalControl.getAttribute('form')).to.equal(externalForm.id);
|
|
477
|
+
document.body.appendChild(externalControl);
|
|
478
|
+
expect(externalControl.form).to.equal(externalForm);
|
|
479
|
+
expect(new FormData(externalForm).get('external')).to.equal('submitted');
|
|
480
|
+
|
|
481
|
+
const nestedForm = document.createElement('form');
|
|
482
|
+
const fieldset = document.createElement('fieldset');
|
|
483
|
+
const nestedControl = document.createElement(htmlTAG);
|
|
484
|
+
nestedControl.setAttribute('name', 'nested');
|
|
485
|
+
fieldset.appendChild(nestedControl);
|
|
486
|
+
nestedForm.appendChild(fieldset);
|
|
487
|
+
document.body.appendChild(nestedForm);
|
|
488
|
+
nestedControl.value = 'nested-value';
|
|
489
|
+
|
|
490
|
+
fieldset.disabled = true;
|
|
491
|
+
await Promise.resolve();
|
|
492
|
+
expect(nestedControl.hasAttribute('disabled')).to.be.false;
|
|
493
|
+
expect(new FormData(nestedForm).get('nested')).to.equal(null);
|
|
494
|
+
|
|
495
|
+
fieldset.disabled = false;
|
|
496
|
+
await Promise.resolve();
|
|
497
|
+
expect(nestedControl.hasAttribute('disabled')).to.be.false;
|
|
498
|
+
expect(new FormData(nestedForm).get('nested')).to.equal('nested-value');
|
|
499
|
+
|
|
500
|
+
externalControl.remove();
|
|
501
|
+
externalForm.remove();
|
|
502
|
+
nestedForm.remove();
|
|
503
|
+
});
|
|
504
|
+
|
|
322
505
|
|
|
323
506
|
});
|
|
324
507
|
});
|
|
325
508
|
|
|
326
509
|
});
|
|
327
|
-
|
|
@@ -154,6 +154,68 @@ describe('DOM', function () {
|
|
|
154
154
|
expect(control.getOption('test')).is.eql(3);
|
|
155
155
|
expect(control.hasAttribute('data-monster-error')).is.false;
|
|
156
156
|
});
|
|
157
|
+
|
|
158
|
+
it('should resolve a changed script host attribute issue #506', function () {
|
|
159
|
+
const mocks = document.getElementById('mocks');
|
|
160
|
+
mocks.innerHTML = `
|
|
161
|
+
<div id="call-back-host-a"></div>
|
|
162
|
+
<div id="call-back-host-b"></div>
|
|
163
|
+
<div id="container"></div>
|
|
164
|
+
`;
|
|
165
|
+
document.getElementById('call-back-host-a').resolveHost = () => 'a';
|
|
166
|
+
document.getElementById('call-back-host-b').resolveHost = () => 'b';
|
|
167
|
+
|
|
168
|
+
const control = document.createElement(randomTagNumber);
|
|
169
|
+
control.setAttribute('data-monster-script-host', 'call-back-host-a');
|
|
170
|
+
document.getElementById('container').appendChild(control);
|
|
171
|
+
|
|
172
|
+
expect(control.callCallback('resolveHost', [])).to.equal('a');
|
|
173
|
+
control.setAttribute('data-monster-script-host', 'call-back-host-b');
|
|
174
|
+
expect(control.callCallback('resolveHost', [])).to.equal('b');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should resolve a replacement host with the same id issue #506', function () {
|
|
178
|
+
const mocks = document.getElementById('mocks');
|
|
179
|
+
mocks.innerHTML = `
|
|
180
|
+
<div id="replaceable-host"></div>
|
|
181
|
+
<div id="container"></div>
|
|
182
|
+
`;
|
|
183
|
+
const original = document.getElementById('replaceable-host');
|
|
184
|
+
original.resolveHost = () => 'original';
|
|
185
|
+
const control = document.createElement(randomTagNumber);
|
|
186
|
+
control.setAttribute('data-monster-script-host', 'replaceable-host');
|
|
187
|
+
document.getElementById('container').appendChild(control);
|
|
188
|
+
expect(control.callCallback('resolveHost', [])).to.equal('original');
|
|
189
|
+
|
|
190
|
+
const replacement = document.createElement('div');
|
|
191
|
+
replacement.id = 'replaceable-host';
|
|
192
|
+
replacement.resolveHost = () => 'replacement';
|
|
193
|
+
original.replaceWith(replacement);
|
|
194
|
+
|
|
195
|
+
expect(control.callCallback('resolveHost', [])).to.equal('replacement');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should resolve the current ancestor after reparenting issue #506', function () {
|
|
199
|
+
const mocks = document.getElementById('mocks');
|
|
200
|
+
mocks.innerHTML = `
|
|
201
|
+
<div id="ancestor-host"><div id="first-container"></div></div>
|
|
202
|
+
<div><div id="second-container"></div></div>
|
|
203
|
+
`;
|
|
204
|
+
const firstHost = document.getElementById('ancestor-host');
|
|
205
|
+
firstHost.resolveHost = () => 'first';
|
|
206
|
+
const control = document.createElement(randomTagNumber);
|
|
207
|
+
control.setAttribute('data-monster-script-host', 'ancestor-host');
|
|
208
|
+
document.getElementById('first-container').appendChild(control);
|
|
209
|
+
expect(control.callCallback('resolveHost', [])).to.equal('first');
|
|
210
|
+
|
|
211
|
+
firstHost.removeAttribute('id');
|
|
212
|
+
const secondHost = document.getElementById('second-container').parentElement;
|
|
213
|
+
secondHost.id = 'ancestor-host';
|
|
214
|
+
secondHost.resolveHost = () => 'second';
|
|
215
|
+
document.getElementById('second-container').appendChild(control);
|
|
216
|
+
|
|
217
|
+
expect(control.callCallback('resolveHost', [])).to.equal('second');
|
|
218
|
+
});
|
|
157
219
|
})
|
|
158
220
|
|
|
159
221
|
});
|
|
@@ -280,6 +280,25 @@ describe('DOM', function () {
|
|
|
280
280
|
expect(monster.getOption('demotest')).is.eql(1425);
|
|
281
281
|
|
|
282
282
|
});
|
|
283
|
+
|
|
284
|
+
it('should not pollute Object.prototype through data-monster-options issue #499', function () {
|
|
285
|
+
delete Object.prototype.monsterCustomElementPolluted;
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
const element = document.createElement('monster-testclass2');
|
|
289
|
+
element.setAttribute(
|
|
290
|
+
'data-monster-options',
|
|
291
|
+
'{"__proto__":{"monsterCustomElementPolluted":"yes"},"demotest":42}',
|
|
292
|
+
);
|
|
293
|
+
document.getElementById('test1').appendChild(element);
|
|
294
|
+
|
|
295
|
+
expect(Object.prototype.monsterCustomElementPolluted).to.equal(undefined);
|
|
296
|
+
expect(({}).monsterCustomElementPolluted).to.equal(undefined);
|
|
297
|
+
expect(element.getOption('demotest')).to.equal(42);
|
|
298
|
+
} finally {
|
|
299
|
+
delete Object.prototype.monsterCustomElementPolluted;
|
|
300
|
+
}
|
|
301
|
+
});
|
|
283
302
|
});
|
|
284
303
|
|
|
285
304
|
describe('create', function () {
|
|
@@ -402,6 +421,50 @@ describe('DOM', function () {
|
|
|
402
421
|
}
|
|
403
422
|
}, 10);
|
|
404
423
|
});
|
|
424
|
+
|
|
425
|
+
it('should keep runtime option attributes typed and reversible issue #504', async function () {
|
|
426
|
+
const htmlTAG = 'monster-testclass-runtime-options';
|
|
427
|
+
|
|
428
|
+
class RuntimeOptionsComponent extends CustomElement {
|
|
429
|
+
static getTag() {
|
|
430
|
+
return htmlTAG;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
get defaults() {
|
|
434
|
+
return Object.assign({}, super.defaults, {
|
|
435
|
+
shadowMode: false,
|
|
436
|
+
count: 1,
|
|
437
|
+
config: {
|
|
438
|
+
enabled: true,
|
|
439
|
+
},
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
registerCustomElement(RuntimeOptionsComponent);
|
|
445
|
+
const element = document.createElement(htmlTAG);
|
|
446
|
+
document.getElementById('test1').appendChild(element);
|
|
447
|
+
|
|
448
|
+
element.setOption('count', null);
|
|
449
|
+
element.setAttribute('data-monster-option-count', '5');
|
|
450
|
+
element.setOption('config', null);
|
|
451
|
+
element.setAttribute(
|
|
452
|
+
'data-monster-option-config',
|
|
453
|
+
'{"enabled":false}',
|
|
454
|
+
);
|
|
455
|
+
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
456
|
+
|
|
457
|
+
expect(element.getOption('count')).to.equal(5);
|
|
458
|
+
expect(typeof element.getOption('count')).to.equal('number');
|
|
459
|
+
expect(element.getOption('config')).to.deep.equal({ enabled: false });
|
|
460
|
+
|
|
461
|
+
element.removeAttribute('data-monster-option-count');
|
|
462
|
+
element.removeAttribute('data-monster-option-config');
|
|
463
|
+
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
464
|
+
|
|
465
|
+
expect(element.getOption('count')).to.equal(1);
|
|
466
|
+
expect(element.getOption('config')).to.deep.equal({ enabled: true });
|
|
467
|
+
});
|
|
405
468
|
});
|
|
406
469
|
|
|
407
470
|
describe('Options change', function () {
|
|
@@ -796,7 +859,7 @@ describe('DOM', function () {
|
|
|
796
859
|
|
|
797
860
|
})
|
|
798
861
|
|
|
799
|
-
|
|
862
|
+
describe('mutation observer lifecycle', function () {
|
|
800
863
|
it('should not rerun linked updaters after disconnect', function (done) {
|
|
801
864
|
let mocks = document.getElementById('mocks');
|
|
802
865
|
mocks.innerHTML = `<monster-testclass-mutation id="mutation-case"></monster-testclass-mutation>`;
|
|
@@ -843,7 +906,65 @@ describe('DOM', function () {
|
|
|
843
906
|
done(e);
|
|
844
907
|
}
|
|
845
908
|
}, 20);
|
|
846
|
-
|
|
909
|
+
})
|
|
910
|
+
|
|
911
|
+
describe('dynamic disabled synchronization issue #505', function () {
|
|
912
|
+
it('should synchronize late controls and preserve child-owned disabled state', async function () {
|
|
913
|
+
const htmlTAG = 'monster-testclass-dynamic-disabled';
|
|
914
|
+
|
|
915
|
+
class DynamicDisabledComponent extends CustomElement {
|
|
916
|
+
static getTag() {
|
|
917
|
+
return htmlTAG;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
get defaults() {
|
|
921
|
+
return Object.assign({}, super.defaults, {
|
|
922
|
+
templates: {
|
|
923
|
+
main: '<input id="initial"><slot></slot>',
|
|
924
|
+
},
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
registerCustomElement(DynamicDisabledComponent);
|
|
930
|
+
const element = document.createElement(htmlTAG);
|
|
931
|
+
const childOwned = document.createElement('input');
|
|
932
|
+
childOwned.setAttribute('disabled', '');
|
|
933
|
+
element.appendChild(childOwned);
|
|
934
|
+
document.getElementById('test1').appendChild(element);
|
|
935
|
+
|
|
936
|
+
const initial = element.shadowRoot.querySelector('#initial');
|
|
937
|
+
element.setOption('disabled', true);
|
|
938
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
939
|
+
expect(initial.disabled).to.equal(true);
|
|
940
|
+
expect(childOwned.disabled).to.equal(true);
|
|
941
|
+
|
|
942
|
+
const lateSlotted = document.createElement('input');
|
|
943
|
+
element.appendChild(lateSlotted);
|
|
944
|
+
const lateShadow = document.createElement('input');
|
|
945
|
+
element.shadowRoot.appendChild(lateShadow);
|
|
946
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
947
|
+
|
|
948
|
+
expect(lateSlotted.disabled).to.equal(true);
|
|
949
|
+
expect(lateShadow.disabled).to.equal(true);
|
|
950
|
+
|
|
951
|
+
lateSlotted.remove();
|
|
952
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
953
|
+
expect(lateSlotted.disabled).to.equal(false);
|
|
954
|
+
|
|
955
|
+
element.appendChild(lateSlotted);
|
|
956
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
957
|
+
expect(lateSlotted.disabled).to.equal(true);
|
|
958
|
+
|
|
959
|
+
element.setOption('disabled', false);
|
|
960
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
961
|
+
|
|
962
|
+
expect(initial.disabled).to.equal(false);
|
|
963
|
+
expect(lateSlotted.disabled).to.equal(false);
|
|
964
|
+
expect(lateShadow.disabled).to.equal(false);
|
|
965
|
+
expect(childOwned.disabled).to.equal(true);
|
|
966
|
+
});
|
|
967
|
+
});
|
|
847
968
|
})
|
|
848
969
|
|
|
849
970
|
});
|