@schukai/monster 4.148.2 → 4.148.4
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/form/select.mjs +104 -16
- package/source/components/state/state.mjs +2 -0
- package/source/data/extend.mjs +7 -1
- package/source/dom/customcontrol.mjs +28 -23
- package/source/dom/customelement.mjs +105 -39
- 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/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/components/form/select.mjs +275 -0
- package/test/cases/data/extend.mjs +27 -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/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
|
@@ -97,6 +97,14 @@ function configureRemotePaginatedSelect(select) {
|
|
|
97
97
|
select.setOption('mapping.objectsPerPage', 'pagination.perPage');
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
function configureLazySelect(select) {
|
|
101
|
+
select.setOption('url', 'https://example.com/lazy-options');
|
|
102
|
+
select.setOption('features.lazyLoad', true);
|
|
103
|
+
select.setOption('mapping.selector', 'items.*');
|
|
104
|
+
select.setOption('mapping.labelTemplate', '${name}');
|
|
105
|
+
select.setOption('mapping.valueTemplate', '${id}');
|
|
106
|
+
}
|
|
107
|
+
|
|
100
108
|
let Select,
|
|
101
109
|
SelectStyleSheet,
|
|
102
110
|
getDefaultSelectPopperPositionProfile,
|
|
@@ -643,6 +651,223 @@ describe('Select', function () {
|
|
|
643
651
|
|
|
644
652
|
});
|
|
645
653
|
|
|
654
|
+
describe('Lazy load lifecycle', function () {
|
|
655
|
+
this.timeout(5000);
|
|
656
|
+
|
|
657
|
+
afterEach(() => {
|
|
658
|
+
const mocks = document.getElementById('mocks');
|
|
659
|
+
mocks.innerHTML = '';
|
|
660
|
+
global['fetch'] = fetchReference;
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
it('should open from the first click after a delayed lazy response renders', async function () {
|
|
664
|
+
const deferredResponse = createDeferred();
|
|
665
|
+
let requestCount = 0;
|
|
666
|
+
global['fetch'] = function () {
|
|
667
|
+
requestCount += 1;
|
|
668
|
+
return deferredResponse.promise;
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
const mocks = document.getElementById('mocks');
|
|
672
|
+
const select = document.createElement('monster-select');
|
|
673
|
+
configureLazySelect(select);
|
|
674
|
+
mocks.appendChild(select);
|
|
675
|
+
|
|
676
|
+
await waitForCondition(() => {
|
|
677
|
+
return select.shadowRoot.querySelector('[data-monster-role=container]') instanceof HTMLElement;
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
const container = select.shadowRoot.querySelector('[data-monster-role=container]');
|
|
681
|
+
const popper = select.shadowRoot.querySelector('[data-monster-role=popper]');
|
|
682
|
+
container.click();
|
|
683
|
+
|
|
684
|
+
await waitForCondition(() => requestCount === 1);
|
|
685
|
+
container.click();
|
|
686
|
+
await new Promise(resolve => setTimeout(resolve, 250));
|
|
687
|
+
expect(requestCount).to.equal(1);
|
|
688
|
+
deferredResponse.resolve(
|
|
689
|
+
await createJsonResponse({
|
|
690
|
+
items: [{id: 'alpha', name: 'Alpha'}]
|
|
691
|
+
})
|
|
692
|
+
);
|
|
693
|
+
|
|
694
|
+
await waitForCondition(() => {
|
|
695
|
+
return select.shadowRoot.querySelectorAll('[data-monster-role=option]').length === 1;
|
|
696
|
+
});
|
|
697
|
+
await waitForCondition(() => popper.style.display === 'block');
|
|
698
|
+
|
|
699
|
+
expect(requestCount).to.equal(1);
|
|
700
|
+
expect(popper.style.display).to.equal('block');
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
it('should keep a failed lazy load in the error state', async function () {
|
|
704
|
+
let requestCount = 0;
|
|
705
|
+
global['fetch'] = function () {
|
|
706
|
+
requestCount += 1;
|
|
707
|
+
return createJsonResponse({}, 500);
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
const mocks = document.getElementById('mocks');
|
|
711
|
+
const select = document.createElement('monster-select');
|
|
712
|
+
configureLazySelect(select);
|
|
713
|
+
mocks.appendChild(select);
|
|
714
|
+
|
|
715
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
716
|
+
select.shadowRoot.querySelector('[data-monster-role=container]').click();
|
|
717
|
+
|
|
718
|
+
await waitForCondition(() => requestCount === 1);
|
|
719
|
+
await waitForCondition(() => {
|
|
720
|
+
return (select.getAttribute('data-monster-error') ?? '').includes('HTTP error! status: 500');
|
|
721
|
+
});
|
|
722
|
+
await new Promise(resolve => setTimeout(resolve, 350));
|
|
723
|
+
|
|
724
|
+
expect(select.getOption('classes.statusOrRemoveBadge')).to.equal('error');
|
|
725
|
+
expect(select.getAttribute('data-monster-error')).to.not.contain('No options available.');
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
it('should retry a failed lazy load on the next click', async function () {
|
|
729
|
+
let requestCount = 0;
|
|
730
|
+
global['fetch'] = function () {
|
|
731
|
+
requestCount += 1;
|
|
732
|
+
if (requestCount === 1) {
|
|
733
|
+
return createJsonResponse({}, 500);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
return createJsonResponse({
|
|
737
|
+
items: [{id: 'alpha', name: 'Alpha'}]
|
|
738
|
+
});
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
const mocks = document.getElementById('mocks');
|
|
742
|
+
const select = document.createElement('monster-select');
|
|
743
|
+
configureLazySelect(select);
|
|
744
|
+
mocks.appendChild(select);
|
|
745
|
+
|
|
746
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
747
|
+
const container = select.shadowRoot.querySelector('[data-monster-role=container]');
|
|
748
|
+
const popper = select.shadowRoot.querySelector('[data-monster-role=popper]');
|
|
749
|
+
container.click();
|
|
750
|
+
|
|
751
|
+
await waitForCondition(() => requestCount === 1);
|
|
752
|
+
await waitForCondition(() => {
|
|
753
|
+
return (select.getAttribute('data-monster-error') ?? '').includes('HTTP error! status: 500');
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
container.click();
|
|
757
|
+
|
|
758
|
+
await waitForCondition(() => requestCount === 2, {timeout: 1500});
|
|
759
|
+
await waitForCondition(() => {
|
|
760
|
+
return select.shadowRoot.querySelectorAll('[data-monster-role=option]').length === 1;
|
|
761
|
+
});
|
|
762
|
+
await waitForCondition(() => popper.style.display === 'block');
|
|
763
|
+
|
|
764
|
+
expect(requestCount).to.equal(2);
|
|
765
|
+
expect(select.getAttribute('data-monster-error') ?? '').to.not.contain('HTTP error! status: 500');
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
it('should retry lazy loading after disconnecting during the request', async function () {
|
|
769
|
+
const firstResponse = createDeferred();
|
|
770
|
+
let requestCount = 0;
|
|
771
|
+
global['fetch'] = function () {
|
|
772
|
+
requestCount += 1;
|
|
773
|
+
if (requestCount === 1) {
|
|
774
|
+
return firstResponse.promise;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
return createJsonResponse({
|
|
778
|
+
items: [{id: 'fresh', name: 'Fresh'}]
|
|
779
|
+
});
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
const mocks = document.getElementById('mocks');
|
|
783
|
+
const select = document.createElement('monster-select');
|
|
784
|
+
configureLazySelect(select);
|
|
785
|
+
mocks.appendChild(select);
|
|
786
|
+
|
|
787
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
788
|
+
const container = select.shadowRoot.querySelector('[data-monster-role=container]');
|
|
789
|
+
container.click();
|
|
790
|
+
await waitForCondition(() => requestCount === 1);
|
|
791
|
+
|
|
792
|
+
select.remove();
|
|
793
|
+
mocks.appendChild(select);
|
|
794
|
+
firstResponse.resolve(
|
|
795
|
+
await createJsonResponse({
|
|
796
|
+
items: [{id: 'stale', name: 'Stale'}]
|
|
797
|
+
})
|
|
798
|
+
);
|
|
799
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
800
|
+
|
|
801
|
+
container.click();
|
|
802
|
+
|
|
803
|
+
await waitForCondition(() => requestCount === 2, {timeout: 1500});
|
|
804
|
+
await waitForCondition(() => {
|
|
805
|
+
return select.getOption('options').map(option => option.value).includes('fresh');
|
|
806
|
+
});
|
|
807
|
+
expect(select.getOption('options').map(option => option.value)).to.deep.equal(['fresh']);
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
it('should not start a delayed lazy request after disconnecting', async function () {
|
|
811
|
+
let requestCount = 0;
|
|
812
|
+
global['fetch'] = function () {
|
|
813
|
+
requestCount += 1;
|
|
814
|
+
return createJsonResponse({
|
|
815
|
+
items: [{id: 'fresh', name: 'Fresh'}]
|
|
816
|
+
});
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
const mocks = document.getElementById('mocks');
|
|
820
|
+
const select = document.createElement('monster-select');
|
|
821
|
+
configureLazySelect(select);
|
|
822
|
+
mocks.appendChild(select);
|
|
823
|
+
|
|
824
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
825
|
+
const container = select.shadowRoot.querySelector('[data-monster-role=container]');
|
|
826
|
+
container.click();
|
|
827
|
+
select.remove();
|
|
828
|
+
|
|
829
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
830
|
+
expect(requestCount).to.equal(0);
|
|
831
|
+
|
|
832
|
+
mocks.appendChild(select);
|
|
833
|
+
container.click();
|
|
834
|
+
await waitForCondition(() => requestCount === 1);
|
|
835
|
+
await waitForCondition(() => {
|
|
836
|
+
return select.getOption('options').map(option => option.value).includes('fresh');
|
|
837
|
+
});
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
it('should not reload a completed lazy select after reconnecting', async function () {
|
|
841
|
+
let requestCount = 0;
|
|
842
|
+
global['fetch'] = function () {
|
|
843
|
+
requestCount += 1;
|
|
844
|
+
return createJsonResponse({
|
|
845
|
+
items: [{id: 'alpha', name: 'Alpha'}]
|
|
846
|
+
});
|
|
847
|
+
};
|
|
848
|
+
|
|
849
|
+
const mocks = document.getElementById('mocks');
|
|
850
|
+
const select = document.createElement('monster-select');
|
|
851
|
+
configureLazySelect(select);
|
|
852
|
+
mocks.appendChild(select);
|
|
853
|
+
|
|
854
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
855
|
+
const container = select.shadowRoot.querySelector('[data-monster-role=container]');
|
|
856
|
+
const popper = select.shadowRoot.querySelector('[data-monster-role=popper]');
|
|
857
|
+
container.click();
|
|
858
|
+
await waitForCondition(() => popper.style.display === 'block');
|
|
859
|
+
|
|
860
|
+
container.click();
|
|
861
|
+
await waitForCondition(() => popper.style.display === 'none');
|
|
862
|
+
select.remove();
|
|
863
|
+
mocks.appendChild(select);
|
|
864
|
+
container.click();
|
|
865
|
+
await waitForCondition(() => popper.style.display === 'block');
|
|
866
|
+
|
|
867
|
+
expect(requestCount).to.equal(1);
|
|
868
|
+
});
|
|
869
|
+
});
|
|
870
|
+
|
|
646
871
|
describe('Popper sizing', function () {
|
|
647
872
|
this.timeout(5000);
|
|
648
873
|
|
|
@@ -1701,6 +1926,56 @@ describe('Select', function () {
|
|
|
1701
1926
|
});
|
|
1702
1927
|
});
|
|
1703
1928
|
|
|
1929
|
+
describe('Remote open lifecycle', function () {
|
|
1930
|
+
this.timeout(5000);
|
|
1931
|
+
|
|
1932
|
+
afterEach(() => {
|
|
1933
|
+
const mocks = document.getElementById('mocks');
|
|
1934
|
+
mocks.innerHTML = '';
|
|
1935
|
+
global['fetch'] = fetchReference;
|
|
1936
|
+
});
|
|
1937
|
+
|
|
1938
|
+
it('should request an immediately entered remote filter only once', async function () {
|
|
1939
|
+
const requests = [];
|
|
1940
|
+
global['fetch'] = function (url) {
|
|
1941
|
+
requests.push(String(url));
|
|
1942
|
+
return createJsonResponse({
|
|
1943
|
+
items: [{id: 'alpha', name: 'Alpha'}]
|
|
1944
|
+
});
|
|
1945
|
+
};
|
|
1946
|
+
|
|
1947
|
+
const mocks = document.getElementById('mocks');
|
|
1948
|
+
const select = document.createElement('monster-select');
|
|
1949
|
+
select.setOption('url', 'https://example.com/items?filter={filter}&page={page}');
|
|
1950
|
+
select.setOption('filter.mode', 'remote');
|
|
1951
|
+
select.setOption('filter.position', 'popper');
|
|
1952
|
+
select.setOption('mapping.selector', 'items.*');
|
|
1953
|
+
select.setOption('mapping.labelTemplate', '${name}');
|
|
1954
|
+
select.setOption('mapping.valueTemplate', '${id}');
|
|
1955
|
+
mocks.appendChild(select);
|
|
1956
|
+
|
|
1957
|
+
await waitForCondition(() => {
|
|
1958
|
+
return select.shadowRoot.querySelector('[data-monster-role=filter][name="popper-filter"]') instanceof HTMLInputElement;
|
|
1959
|
+
});
|
|
1960
|
+
|
|
1961
|
+
const container = select.shadowRoot.querySelector('[data-monster-role=container]');
|
|
1962
|
+
const filterInput = select.shadowRoot.querySelector('[data-monster-role=filter][name="popper-filter"]');
|
|
1963
|
+
container.click();
|
|
1964
|
+
filterInput.value = 'alpha';
|
|
1965
|
+
filterInput.dispatchEvent(new Event('input', {
|
|
1966
|
+
bubbles: true,
|
|
1967
|
+
composed: true
|
|
1968
|
+
}));
|
|
1969
|
+
|
|
1970
|
+
await waitForCondition(() => requests.length >= 1);
|
|
1971
|
+
await new Promise(resolve => setTimeout(resolve, 350));
|
|
1972
|
+
|
|
1973
|
+
expect(requests).to.deep.equal([
|
|
1974
|
+
'https://example.com/items?filter=alpha&page=1'
|
|
1975
|
+
]);
|
|
1976
|
+
});
|
|
1977
|
+
});
|
|
1978
|
+
|
|
1704
1979
|
describe('document.createElement()', function () {
|
|
1705
1980
|
|
|
1706
1981
|
afterEach(() => {
|
|
@@ -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
|
|
|
@@ -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
|
});
|