@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
|
@@ -587,6 +587,154 @@ describe('DOM', function () {
|
|
|
587
587
|
linkedUpdater[internalSymbol].subject.detachObserver(observer);
|
|
588
588
|
}
|
|
589
589
|
});
|
|
590
|
+
|
|
591
|
+
it('should not re-render unrelated NaN option paths on every change issue #571', async function () {
|
|
592
|
+
const htmlTAG = 'monster-testclass-nan-identity-571';
|
|
593
|
+
|
|
594
|
+
if (!customElements.get(htmlTAG)) {
|
|
595
|
+
class NanIdentityComponent extends CustomElement {
|
|
596
|
+
static getTag() {
|
|
597
|
+
return htmlTAG;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
get defaults() {
|
|
601
|
+
return Object.assign({}, super.defaults, {
|
|
602
|
+
measured: NaN,
|
|
603
|
+
unrelated: 0,
|
|
604
|
+
templates: {
|
|
605
|
+
main: '<span id="nan-measured" data-monster-replace="path:measured | call:renderMeasured"></span><span id="nan-unrelated" data-monster-replace="path:unrelated"></span>',
|
|
606
|
+
},
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
[updaterTransformerMethodsSymbol]() {
|
|
611
|
+
return {
|
|
612
|
+
renderMeasured: (value) => {
|
|
613
|
+
this.renderCalls = (this.renderCalls || 0) + 1;
|
|
614
|
+
return Number.isNaN(value) ? 'n/a' : String(value);
|
|
615
|
+
},
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
registerCustomElement(NanIdentityComponent);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const element = document.createElement(htmlTAG);
|
|
624
|
+
document.getElementById('test1').appendChild(element);
|
|
625
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
626
|
+
|
|
627
|
+
expect(element.shadowRoot.querySelector('#nan-measured').textContent).to.equal('n/a');
|
|
628
|
+
const afterAssembly = element.renderCalls;
|
|
629
|
+
expect(afterAssembly).to.be.greaterThan(0);
|
|
630
|
+
|
|
631
|
+
element.setOption('unrelated', 1);
|
|
632
|
+
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
633
|
+
|
|
634
|
+
expect(element.shadowRoot.querySelector('#nan-unrelated').textContent).to.equal('1');
|
|
635
|
+
expect(element.renderCalls).to.equal(afterAssembly);
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
it('should not accumulate render work for a NaN option across repeated changes issue #571', async function () {
|
|
639
|
+
const htmlTAG = 'monster-testclass-nan-identity-repeat-571';
|
|
640
|
+
|
|
641
|
+
if (!customElements.get(htmlTAG)) {
|
|
642
|
+
class NanRepeatComponent extends CustomElement {
|
|
643
|
+
static getTag() {
|
|
644
|
+
return htmlTAG;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
get defaults() {
|
|
648
|
+
return Object.assign({}, super.defaults, {
|
|
649
|
+
measured: NaN,
|
|
650
|
+
counter: 0,
|
|
651
|
+
templates: {
|
|
652
|
+
main: '<span id="nan-repeat-measured" data-monster-replace="path:measured | call:renderMeasured"></span><span id="nan-repeat-counter" data-monster-replace="path:counter"></span>',
|
|
653
|
+
},
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
[updaterTransformerMethodsSymbol]() {
|
|
658
|
+
return {
|
|
659
|
+
renderMeasured: (value) => {
|
|
660
|
+
this.renderCalls = (this.renderCalls || 0) + 1;
|
|
661
|
+
return Number.isNaN(value) ? 'n/a' : String(value);
|
|
662
|
+
},
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
registerCustomElement(NanRepeatComponent);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const element = document.createElement(htmlTAG);
|
|
671
|
+
document.getElementById('test1').appendChild(element);
|
|
672
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
673
|
+
|
|
674
|
+
const afterAssembly = element.renderCalls;
|
|
675
|
+
|
|
676
|
+
for (let i = 1; i <= 5; i++) {
|
|
677
|
+
element.setOption('counter', i);
|
|
678
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
expect(element.shadowRoot.querySelector('#nan-repeat-counter').textContent).to.equal('5');
|
|
682
|
+
expect(element.renderCalls).to.equal(afterAssembly);
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
it('should remove stale updater keys after replacing an option object issue #565', async function () {
|
|
686
|
+
const htmlTAG = 'monster-testclass-object-sync-565';
|
|
687
|
+
|
|
688
|
+
if (!customElements.get(htmlTAG)) {
|
|
689
|
+
class ObjectSyncComponent extends CustomElement {
|
|
690
|
+
static getTag() {
|
|
691
|
+
return htmlTAG;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
get defaults() {
|
|
695
|
+
return Object.assign({}, super.defaults, {
|
|
696
|
+
settings: {a: 'initial', b: 'retained'},
|
|
697
|
+
templates: {
|
|
698
|
+
main: '<span data-monster-replace="path:settings.a"></span>',
|
|
699
|
+
},
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
registerCustomElement(ObjectSyncComponent);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const element = document.createElement(htmlTAG);
|
|
708
|
+
document.getElementById('test1').appendChild(element);
|
|
709
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
710
|
+
|
|
711
|
+
let linkedUpdater;
|
|
712
|
+
for (const group of element[customElementUpdaterLinkSymbol]) {
|
|
713
|
+
for (const updater of group) {
|
|
714
|
+
linkedUpdater = updater;
|
|
715
|
+
break;
|
|
716
|
+
}
|
|
717
|
+
if (linkedUpdater) break;
|
|
718
|
+
}
|
|
719
|
+
expect(linkedUpdater).to.exist;
|
|
720
|
+
|
|
721
|
+
element.setOptions({settings: {a: 'merged'}});
|
|
722
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
723
|
+
expect(linkedUpdater.getSubject().settings).to.deep.include({
|
|
724
|
+
a: 'merged',
|
|
725
|
+
b: 'retained',
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
const settingsProxy = linkedUpdater.getSubject().settings;
|
|
729
|
+
element.setOption('settings', {a: 'replacement'});
|
|
730
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
731
|
+
|
|
732
|
+
expect(linkedUpdater.getSubject().settings).to.equal(settingsProxy);
|
|
733
|
+
expect(linkedUpdater.getSubject().settings).to.deep.equal({
|
|
734
|
+
a: 'replacement',
|
|
735
|
+
});
|
|
736
|
+
expect(element.shadowRoot.textContent).to.equal('replacement');
|
|
737
|
+
});
|
|
590
738
|
})
|
|
591
739
|
|
|
592
740
|
describe('setOptions()', function () {
|
|
@@ -932,10 +1080,270 @@ describe('DOM', function () {
|
|
|
932
1080
|
expect(d.hasNode(t1)).to.be.true;
|
|
933
1081
|
|
|
934
1082
|
})
|
|
1083
|
+
// net: containment that already works and must keep working
|
|
1084
|
+
it("hasNode should stay false for detached and foreign nodes issue #574", function () {
|
|
1085
|
+
let d = document.createElement('monster-testclass');
|
|
1086
|
+
let other = document.createElement('monster-testclass');
|
|
1087
|
+
|
|
1088
|
+
let own = document.createElement('div');
|
|
1089
|
+
let foreign = document.createElement('div');
|
|
1090
|
+
let foreignChild = document.createElement('span');
|
|
1091
|
+
foreign.appendChild(foreignChild);
|
|
1092
|
+
|
|
1093
|
+
d.appendChild(own);
|
|
1094
|
+
other.appendChild(foreign);
|
|
1095
|
+
|
|
1096
|
+
let div = document.getElementById('test1');
|
|
1097
|
+
div.append(d);
|
|
1098
|
+
div.append(other);
|
|
1099
|
+
|
|
1100
|
+
let detached = document.createElement('p');
|
|
1101
|
+
|
|
1102
|
+
expect(d.hasNode(own)).to.be.true;
|
|
1103
|
+
expect(d.hasNode(detached)).to.be.false;
|
|
1104
|
+
expect(d.hasNode(foreign)).to.be.false;
|
|
1105
|
+
expect(d.hasNode(foreignChild)).to.be.false;
|
|
1106
|
+
})
|
|
1107
|
+
|
|
1108
|
+
// regression: a node inside the shadow root of a child custom element
|
|
1109
|
+
it("hasNode should find a node inside the shadow root of a child element issue #574", async function () {
|
|
1110
|
+
const parentTAG = 'monster-testclass-hasnode-574';
|
|
1111
|
+
const childTAG = 'monster-testclass-hasnode-child-574';
|
|
1112
|
+
|
|
1113
|
+
if (!customElements.get(childTAG)) {
|
|
1114
|
+
class HasNodeChild extends CustomElement {
|
|
1115
|
+
static getTag() {
|
|
1116
|
+
return childTAG;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
get defaults() {
|
|
1120
|
+
return Object.assign({}, super.defaults, {
|
|
1121
|
+
templates: {
|
|
1122
|
+
main: '<span id="deep-node">deep</span>',
|
|
1123
|
+
},
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
registerCustomElement(HasNodeChild);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
if (!customElements.get(parentTAG)) {
|
|
1132
|
+
class HasNodeParent extends CustomElement {
|
|
1133
|
+
static getTag() {
|
|
1134
|
+
return parentTAG;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
get defaults() {
|
|
1138
|
+
return Object.assign({}, super.defaults, {
|
|
1139
|
+
templates: {
|
|
1140
|
+
main: '<div id="host-slot"></div>',
|
|
1141
|
+
},
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
registerCustomElement(HasNodeParent);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
const parent = document.createElement(parentTAG);
|
|
1150
|
+
document.getElementById('test1').appendChild(parent);
|
|
1151
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1152
|
+
|
|
1153
|
+
const child = document.createElement(childTAG);
|
|
1154
|
+
parent.shadowRoot.getElementById('host-slot').appendChild(child);
|
|
1155
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1156
|
+
|
|
1157
|
+
const deep = child.shadowRoot.getElementById('deep-node');
|
|
1158
|
+
expect(deep).to.not.equal(null);
|
|
1159
|
+
|
|
1160
|
+
// the child itself lives in the parent shadow root and is found today
|
|
1161
|
+
expect(parent.hasNode(child)).to.be.true;
|
|
1162
|
+
// the node inside the child shadow root is the gap being closed
|
|
1163
|
+
expect(parent.hasNode(deep)).to.be.true;
|
|
1164
|
+
})
|
|
1165
|
+
|
|
1166
|
+
it("hasNode should find a node nested two shadow roots deep issue #574", async function () {
|
|
1167
|
+
const outerTAG = 'monster-testclass-hasnode-outer-574';
|
|
1168
|
+
const middleTAG = 'monster-testclass-hasnode-middle-574';
|
|
1169
|
+
|
|
1170
|
+
if (!customElements.get(middleTAG)) {
|
|
1171
|
+
class HasNodeMiddle extends CustomElement {
|
|
1172
|
+
static getTag() {
|
|
1173
|
+
return middleTAG;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
get defaults() {
|
|
1177
|
+
return Object.assign({}, super.defaults, {
|
|
1178
|
+
templates: {
|
|
1179
|
+
main: '<div id="middle-host"></div>',
|
|
1180
|
+
},
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
registerCustomElement(HasNodeMiddle);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
if (!customElements.get(outerTAG)) {
|
|
1189
|
+
class HasNodeOuter extends CustomElement {
|
|
1190
|
+
static getTag() {
|
|
1191
|
+
return outerTAG;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
get defaults() {
|
|
1195
|
+
return Object.assign({}, super.defaults, {
|
|
1196
|
+
templates: {
|
|
1197
|
+
main: '<div id="outer-host"></div>',
|
|
1198
|
+
},
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
registerCustomElement(HasNodeOuter);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
const outer = document.createElement(outerTAG);
|
|
1207
|
+
document.getElementById('test1').appendChild(outer);
|
|
1208
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1209
|
+
|
|
1210
|
+
const middle = document.createElement(middleTAG);
|
|
1211
|
+
outer.shadowRoot.getElementById('outer-host').appendChild(middle);
|
|
1212
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1213
|
+
|
|
1214
|
+
const leaf = document.createElement('b');
|
|
1215
|
+
middle.shadowRoot.getElementById('middle-host').appendChild(leaf);
|
|
1216
|
+
|
|
1217
|
+
expect(outer.hasNode(leaf)).to.be.true;
|
|
1218
|
+
|
|
1219
|
+
const unrelated = document.createElement('b');
|
|
1220
|
+
expect(outer.hasNode(unrelated)).to.be.false;
|
|
1221
|
+
})
|
|
1222
|
+
|
|
935
1223
|
|
|
936
1224
|
|
|
937
1225
|
})
|
|
938
1226
|
|
|
1227
|
+
describe('option resolution issue #576', function () {
|
|
1228
|
+
|
|
1229
|
+
let mocks;
|
|
1230
|
+
beforeEach(() => {
|
|
1231
|
+
mocks = document.getElementById('mocks');
|
|
1232
|
+
mocks.innerHTML = html1;
|
|
1233
|
+
})
|
|
1234
|
+
|
|
1235
|
+
// Pathfinder.exists() is load bearing as an input validator here:
|
|
1236
|
+
// the option name is undefined for every unknown
|
|
1237
|
+
// data-monster-option-* attribute, and the constructor plus every
|
|
1238
|
+
// attributeChangedCallback runs through it.
|
|
1239
|
+
it('should construct with an unknown data-monster-option attribute', async function () {
|
|
1240
|
+
const htmlTAG = 'monster-testclass-unknown-option-576';
|
|
1241
|
+
|
|
1242
|
+
if (!customElements.get(htmlTAG)) {
|
|
1243
|
+
class UnknownOptionComponent extends CustomElement {
|
|
1244
|
+
static getTag() {
|
|
1245
|
+
return htmlTAG;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
get defaults() {
|
|
1249
|
+
return Object.assign({}, super.defaults, {
|
|
1250
|
+
known: 'initial',
|
|
1251
|
+
templates: {
|
|
1252
|
+
main: '<span id="known-value" data-monster-replace="path:known"></span>',
|
|
1253
|
+
},
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
registerCustomElement(UnknownOptionComponent);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
const element = document.createElement(htmlTAG);
|
|
1262
|
+
element.setAttribute('data-monster-option-foo', 'bar');
|
|
1263
|
+
element.setAttribute('data-monster-option-deeply-unknown-path', '1');
|
|
1264
|
+
document.getElementById('test1').appendChild(element);
|
|
1265
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1266
|
+
|
|
1267
|
+
expect(element.hasAttribute('data-monster-error')).to.be.false;
|
|
1268
|
+
expect(element.shadowRoot.querySelector('#known-value').textContent).to.equal('initial');
|
|
1269
|
+
expect(element.getOption('foo')).to.equal(undefined);
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
it('should survive a runtime change of an unknown data-monster-option attribute', async function () {
|
|
1273
|
+
const htmlTAG = 'monster-testclass-unknown-option-runtime-576';
|
|
1274
|
+
|
|
1275
|
+
if (!customElements.get(htmlTAG)) {
|
|
1276
|
+
class UnknownOptionRuntimeComponent extends CustomElement {
|
|
1277
|
+
static getTag() {
|
|
1278
|
+
return htmlTAG;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
get defaults() {
|
|
1282
|
+
return Object.assign({}, super.defaults, {
|
|
1283
|
+
known: 'initial',
|
|
1284
|
+
templates: {
|
|
1285
|
+
main: '<span id="runtime-value" data-monster-replace="path:known"></span>',
|
|
1286
|
+
},
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
registerCustomElement(UnknownOptionRuntimeComponent);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
const element = document.createElement(htmlTAG);
|
|
1295
|
+
document.getElementById('test1').appendChild(element);
|
|
1296
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1297
|
+
|
|
1298
|
+
element.setAttribute('data-monster-option-foo', 'bar');
|
|
1299
|
+
element.setAttribute('data-monster-option-known', 'changed');
|
|
1300
|
+
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
1301
|
+
|
|
1302
|
+
expect(element.hasAttribute('data-monster-error')).to.be.false;
|
|
1303
|
+
expect(element.getOption('known')).to.equal('changed');
|
|
1304
|
+
expect(element.shadowRoot.querySelector('#runtime-value').textContent).to.equal('changed');
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
it('should return declared defaults including falsy ones', async function () {
|
|
1308
|
+
const htmlTAG = 'monster-testclass-falsy-defaults-576';
|
|
1309
|
+
|
|
1310
|
+
if (!customElements.get(htmlTAG)) {
|
|
1311
|
+
class FalsyDefaultsComponent extends CustomElement {
|
|
1312
|
+
static getTag() {
|
|
1313
|
+
return htmlTAG;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
get defaults() {
|
|
1317
|
+
return Object.assign({}, super.defaults, {
|
|
1318
|
+
zero: 0,
|
|
1319
|
+
flag: false,
|
|
1320
|
+
text: '',
|
|
1321
|
+
nothing: null,
|
|
1322
|
+
absent: undefined,
|
|
1323
|
+
templates: {
|
|
1324
|
+
main: '<span></span>',
|
|
1325
|
+
},
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
registerCustomElement(FalsyDefaultsComponent);
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
const element = document.createElement(htmlTAG);
|
|
1334
|
+
document.getElementById('test1').appendChild(element);
|
|
1335
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1336
|
+
|
|
1337
|
+
expect(element.getOption('zero', 99)).to.equal(0);
|
|
1338
|
+
expect(element.getOption('flag', true)).to.equal(false);
|
|
1339
|
+
expect(element.getOption('text', 'fallback')).to.equal('');
|
|
1340
|
+
expect(element.getOption('nothing', 'fallback')).to.equal(null);
|
|
1341
|
+
expect(element.getOption('absent', 'fallback')).to.equal('fallback');
|
|
1342
|
+
expect(element.getOption('templates.main', 'fallback')).to.equal('<span></span>');
|
|
1343
|
+
expect(element.getOption('missing.deep.path', 'fallback')).to.equal('fallback');
|
|
1344
|
+
});
|
|
1345
|
+
});
|
|
1346
|
+
|
|
939
1347
|
describe('mutation observer lifecycle', function () {
|
|
940
1348
|
it('should coalesce child mutations without a refresh loop issue #517', async function () {
|
|
941
1349
|
const htmlTAG = 'monster-testclass-mutation-loop-517';
|
|
@@ -6,6 +6,21 @@ import {getGlobal} from "../../../source/types/global.mjs";
|
|
|
6
6
|
import {detectRuntimeEnvironment} from "../../../source/util/runtime.mjs";
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Puts the real window back. Deleting the global instead would make
|
|
11
|
+
* initJSDOM() build a second jsdom for every file that runs later, while
|
|
12
|
+
* CustomElement still extends the HTMLElement of the first one, so every
|
|
13
|
+
* custom element defined afterwards fails to upgrade.
|
|
14
|
+
*/
|
|
15
|
+
function restoreWindow(previousWindow) {
|
|
16
|
+
if (previousWindow === undefined) {
|
|
17
|
+
delete getGlobal().window;
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getGlobal().window = previousWindow;
|
|
22
|
+
}
|
|
23
|
+
|
|
9
24
|
function getMockWindow(dpi) {
|
|
10
25
|
|
|
11
26
|
if(detectRuntimeEnvironment() === 'browser') {
|
|
@@ -43,16 +58,19 @@ describe('dimension', () => {
|
|
|
43
58
|
});
|
|
44
59
|
})
|
|
45
60
|
|
|
61
|
+
let originalWindow;
|
|
62
|
+
|
|
46
63
|
beforeEach(() => {
|
|
47
64
|
|
|
48
65
|
const testDpi = 96;
|
|
49
66
|
const testWindow = getMockWindow(testDpi);
|
|
67
|
+
originalWindow = getGlobal().window;
|
|
50
68
|
getGlobal().window = testWindow;
|
|
51
69
|
|
|
52
70
|
});
|
|
53
71
|
|
|
54
72
|
afterEach(() => {
|
|
55
|
-
|
|
73
|
+
restoreWindow(originalWindow);
|
|
56
74
|
});
|
|
57
75
|
|
|
58
76
|
describe('convertToPixels', () => {
|
|
@@ -123,24 +141,30 @@ describe('dimension', () => {
|
|
|
123
141
|
it('should return the correct device DPI', () => {
|
|
124
142
|
const testDpi = 96;
|
|
125
143
|
const testWindow = getMockWindow(testDpi);
|
|
144
|
+
const previousWindow = getGlobal().window;
|
|
126
145
|
getGlobal().window = testWindow;
|
|
127
146
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
147
|
+
try {
|
|
148
|
+
const deviceDpi = getDeviceDPI();
|
|
149
|
+
expect(deviceDpi).to.equal(testDpi * testWindow.devicePixelRatio);
|
|
150
|
+
} finally {
|
|
151
|
+
restoreWindow(previousWindow);
|
|
152
|
+
}
|
|
132
153
|
});
|
|
133
154
|
|
|
134
155
|
it('should cache the result and return the same value', () => {
|
|
135
156
|
const testDpi = 96;
|
|
136
157
|
const testWindow = getMockWindow(testDpi);
|
|
158
|
+
const previousWindow = getGlobal().window;
|
|
137
159
|
getGlobal().window = testWindow;
|
|
138
160
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
161
|
+
try {
|
|
162
|
+
const deviceDpi1 = getDeviceDPI();
|
|
163
|
+
const deviceDpi2 = getDeviceDPI();
|
|
164
|
+
expect(deviceDpi1).to.equal(deviceDpi2);
|
|
165
|
+
} finally {
|
|
166
|
+
restoreWindow(previousWindow);
|
|
167
|
+
}
|
|
144
168
|
});
|
|
145
169
|
});
|
|
146
170
|
|
|
@@ -1,14 +1,61 @@
|
|
|
1
1
|
import {expect} from 'chai';
|
|
2
2
|
import {getSlottedNodes} from "../../../source/dom/slotted.mjs";
|
|
3
|
+
import {getSlottedElements} from "../../../source/dom/slotted.mjs";
|
|
4
|
+
import {initJSDOM} from "../../util/jsdom.mjs";
|
|
3
5
|
|
|
4
6
|
|
|
5
7
|
describe('getSlottedNodes', () => {
|
|
6
8
|
|
|
9
|
+
before(async () => {
|
|
10
|
+
await initJSDOM();
|
|
11
|
+
});
|
|
12
|
+
|
|
7
13
|
|
|
8
14
|
it('should return an empty Set if there is no ShadowRoot', () => {
|
|
9
15
|
expect(getSlottedNodes).is.a('function');
|
|
10
16
|
});
|
|
11
17
|
|
|
18
|
+
it('should ignore text nodes when applying an element query issue #567', () => {
|
|
19
|
+
const host = document.createElement('div');
|
|
20
|
+
host.append(' whitespace ');
|
|
21
|
+
const child = document.createElement('span');
|
|
22
|
+
child.className = 'match';
|
|
23
|
+
host.appendChild(child);
|
|
24
|
+
host.attachShadow({mode: 'open'}).innerHTML = '<slot></slot>';
|
|
25
|
+
document.body.appendChild(host);
|
|
26
|
+
|
|
27
|
+
expect([...getSlottedNodes.call(host, '.match')]).to.deep.equal([child]);
|
|
28
|
+
host.remove();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should match empty and spaced slot names without selector interpolation issue #567', () => {
|
|
32
|
+
const host = document.createElement('div');
|
|
33
|
+
const defaultChild = document.createElement('span');
|
|
34
|
+
const namedChild = document.createElement('span');
|
|
35
|
+
namedChild.slot = 'some slot';
|
|
36
|
+
host.append(defaultChild, namedChild);
|
|
37
|
+
host.attachShadow({mode: 'open'}).innerHTML =
|
|
38
|
+
'<slot name=""></slot><slot name="some slot"></slot>';
|
|
39
|
+
document.body.appendChild(host);
|
|
40
|
+
|
|
41
|
+
expect([...getSlottedElements.call(host, undefined, null)]).to.deep.equal([defaultChild]);
|
|
42
|
+
expect([...getSlottedElements.call(host, undefined, 'some slot')]).to.deep.equal([namedChild]);
|
|
43
|
+
host.remove();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should accept the internally managed closed shadow root issue #567', () => {
|
|
47
|
+
const host = document.createElement('div');
|
|
48
|
+
const child = document.createElement('span');
|
|
49
|
+
host.appendChild(child);
|
|
50
|
+
const root = host.attachShadow({mode: 'closed'});
|
|
51
|
+
root.innerHTML = '<slot></slot>';
|
|
52
|
+
document.body.appendChild(host);
|
|
53
|
+
|
|
54
|
+
expect(host.shadowRoot).to.equal(null);
|
|
55
|
+
expect([...getSlottedElements.call(host, undefined, undefined, root)])
|
|
56
|
+
.to.deep.equal([child]);
|
|
57
|
+
host.remove();
|
|
58
|
+
});
|
|
12
59
|
|
|
13
|
-
});
|
|
14
60
|
|
|
61
|
+
});
|
|
@@ -81,6 +81,17 @@ describe('Template', function () {
|
|
|
81
81
|
expect(t).is.instanceof(Template);
|
|
82
82
|
|
|
83
83
|
})
|
|
84
|
+
|
|
85
|
+
it('should resolve from a text node context issue #567', function () {
|
|
86
|
+
const container = document.createElement('div');
|
|
87
|
+
const text = document.createTextNode('context');
|
|
88
|
+
container.appendChild(text);
|
|
89
|
+
document.body.appendChild(container);
|
|
90
|
+
|
|
91
|
+
const template = findDocumentTemplate('mytemplate', text);
|
|
92
|
+
expect(template).to.be.instanceof(Template);
|
|
93
|
+
container.remove();
|
|
94
|
+
});
|
|
84
95
|
});
|
|
85
96
|
|
|
86
97
|
describe('Thmened Template', function () {
|