@schukai/monster 4.153.1 → 4.154.1
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 +3 -3
- 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 +186 -19
- package/source/dom/customelement.mjs +273 -30
- package/source/dom/updater.mjs +6 -2
- package/source/types/base.mjs +7 -6
- package/source/types/basewithoptions.mjs +1 -8
- package/source/types/internal.mjs +4 -10
- package/source/types/observerlist.mjs +9 -17
- package/source/types/proxyobserver.mjs +19 -2
- 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/datatable/drag-scroll.mjs +41 -9
- package/test/cases/components/navigation/site-navigation.mjs +10 -1
- package/test/cases/components/time/month-calendar.mjs +115 -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 +129 -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 +354 -0
- package/test/cases/dom/dimension.mjs +34 -10
- package/test/cases/dom/updater-remove.mjs +198 -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 +114 -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
|
@@ -588,6 +588,100 @@ describe('DOM', function () {
|
|
|
588
588
|
}
|
|
589
589
|
});
|
|
590
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
|
+
|
|
591
685
|
it('should remove stale updater keys after replacing an option object issue #565', async function () {
|
|
592
686
|
const htmlTAG = 'monster-testclass-object-sync-565';
|
|
593
687
|
|
|
@@ -986,10 +1080,270 @@ describe('DOM', function () {
|
|
|
986
1080
|
expect(d.hasNode(t1)).to.be.true;
|
|
987
1081
|
|
|
988
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
|
+
|
|
989
1223
|
|
|
990
1224
|
|
|
991
1225
|
})
|
|
992
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
|
+
|
|
993
1347
|
describe('mutation observer lifecycle', function () {
|
|
994
1348
|
it('should coalesce child mutations without a refresh loop issue #517', async function () {
|
|
995
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
|
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import * as chai from "chai";
|
|
4
|
+
import { getDocument } from "../../../source/dom/util.mjs";
|
|
5
|
+
import { chaiDom } from "../../util/chai-dom.mjs";
|
|
6
|
+
import { initJSDOM } from "../../util/jsdom.mjs";
|
|
7
|
+
|
|
8
|
+
let expect = chai.expect;
|
|
9
|
+
chai.use(chaiDom);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Regressions for issue #579.
|
|
13
|
+
*
|
|
14
|
+
* data-monster-remove had no coverage anywhere in the repository, and its
|
|
15
|
+
* selector relied on a :scope prefix that carries no meaning for a descendant
|
|
16
|
+
* query but does not resolve for shadow root content under jsdom.
|
|
17
|
+
*/
|
|
18
|
+
describe("Updater remove attribute issue #579", function () {
|
|
19
|
+
let Updater;
|
|
20
|
+
let CustomElement;
|
|
21
|
+
let registerCustomElement;
|
|
22
|
+
let document;
|
|
23
|
+
|
|
24
|
+
const html = `
|
|
25
|
+
<div id="test1"></div>
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
before(function (done) {
|
|
29
|
+
initJSDOM({})
|
|
30
|
+
.then(() => import("../../../source/dom/updater.mjs"))
|
|
31
|
+
.then((updaterModule) => {
|
|
32
|
+
Updater = updaterModule["Updater"];
|
|
33
|
+
return import("../../../source/dom/customelement.mjs");
|
|
34
|
+
})
|
|
35
|
+
.then((m) => {
|
|
36
|
+
try {
|
|
37
|
+
CustomElement = m["CustomElement"];
|
|
38
|
+
registerCustomElement = m["registerCustomElement"];
|
|
39
|
+
document = getDocument();
|
|
40
|
+
done();
|
|
41
|
+
} catch (e) {
|
|
42
|
+
done(e);
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
.catch(done);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
beforeEach(function () {
|
|
49
|
+
document.getElementById("mocks").innerHTML = html;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
afterEach(function () {
|
|
53
|
+
document.getElementById("mocks").innerHTML = "";
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
function settle(ms = 30) {
|
|
57
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
describe("light DOM", function () {
|
|
61
|
+
// net: this path already worked, because :scope resolves outside a shadow
|
|
62
|
+
// root, and it must keep working
|
|
63
|
+
it("should remove annotated nodes and keep the rest", async function () {
|
|
64
|
+
const container = document.getElementById("test1");
|
|
65
|
+
container.innerHTML = `
|
|
66
|
+
<div id="keep-me"></div>
|
|
67
|
+
<div id="drop-me" data-monster-remove></div>
|
|
68
|
+
<span data-monster-replace="path:label"></span>
|
|
69
|
+
`;
|
|
70
|
+
|
|
71
|
+
const updater = new Updater(container, { label: "rendered" });
|
|
72
|
+
await updater.run();
|
|
73
|
+
await settle();
|
|
74
|
+
|
|
75
|
+
expect(container.querySelector("#drop-me")).to.equal(null);
|
|
76
|
+
expect(container.querySelector("#keep-me")).to.not.equal(null);
|
|
77
|
+
expect(container.querySelector("span").textContent).to.equal("rendered");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should remove every annotated node, nested ones included", async function () {
|
|
81
|
+
const container = document.getElementById("test1");
|
|
82
|
+
container.innerHTML = `
|
|
83
|
+
<div id="outer" data-monster-remove>
|
|
84
|
+
<div id="inner" data-monster-remove></div>
|
|
85
|
+
</div>
|
|
86
|
+
<div id="sibling"><div id="deep" data-monster-remove></div></div>
|
|
87
|
+
<span data-monster-replace="path:label"></span>
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
const updater = new Updater(container, { label: "rendered" });
|
|
91
|
+
await updater.run();
|
|
92
|
+
await settle();
|
|
93
|
+
|
|
94
|
+
expect(container.querySelector("#outer")).to.equal(null);
|
|
95
|
+
expect(container.querySelector("#inner")).to.equal(null);
|
|
96
|
+
expect(container.querySelector("#deep")).to.equal(null);
|
|
97
|
+
expect(container.querySelector("#sibling")).to.not.equal(null);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("shadow DOM", function () {
|
|
102
|
+
const TAG = "monster-updater-remove-579";
|
|
103
|
+
|
|
104
|
+
before(function () {
|
|
105
|
+
if (!customElements.get(TAG)) {
|
|
106
|
+
class RemoveComponent extends CustomElement {
|
|
107
|
+
static getTag() {
|
|
108
|
+
return TAG;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
get defaults() {
|
|
112
|
+
return Object.assign({}, super.defaults, {
|
|
113
|
+
label: "rendered",
|
|
114
|
+
templates: {
|
|
115
|
+
main: `
|
|
116
|
+
<div id="host">
|
|
117
|
+
<div id="keep-me"></div>
|
|
118
|
+
<div id="drop-me" data-monster-remove></div>
|
|
119
|
+
<span id="label" data-monster-replace="path:label"></span>
|
|
120
|
+
</div>
|
|
121
|
+
`,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
registerCustomElement(RemoveComponent);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// regression: the annotated node survived inside a shadow root
|
|
132
|
+
it("should remove annotated nodes from a component shadow root", async function () {
|
|
133
|
+
const element = document.createElement(TAG);
|
|
134
|
+
document.getElementById("test1").appendChild(element);
|
|
135
|
+
await settle();
|
|
136
|
+
|
|
137
|
+
expect(element.shadowRoot.querySelector("#drop-me")).to.equal(null);
|
|
138
|
+
expect(element.shadowRoot.querySelector("#keep-me")).to.not.equal(null);
|
|
139
|
+
expect(element.shadowRoot.querySelector("#label").textContent).to.equal(
|
|
140
|
+
"rendered",
|
|
141
|
+
);
|
|
142
|
+
expect(element.hasAttribute("data-monster-error")).to.be.false;
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("should keep the shadow root free of annotated nodes after an update", async function () {
|
|
146
|
+
const element = document.createElement(TAG);
|
|
147
|
+
document.getElementById("test1").appendChild(element);
|
|
148
|
+
await settle();
|
|
149
|
+
|
|
150
|
+
element.setOption("label", "changed");
|
|
151
|
+
await settle();
|
|
152
|
+
|
|
153
|
+
expect(element.shadowRoot.querySelector("#label").textContent).to.equal(
|
|
154
|
+
"changed",
|
|
155
|
+
);
|
|
156
|
+
expect(
|
|
157
|
+
element.shadowRoot.querySelectorAll("[data-monster-remove]").length,
|
|
158
|
+
).to.equal(0);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("should remove an annotated node nested below the updater element", async function () {
|
|
162
|
+
const nestedTag = "monster-updater-remove-nested-579";
|
|
163
|
+
|
|
164
|
+
if (!customElements.get(nestedTag)) {
|
|
165
|
+
class NestedRemoveComponent extends CustomElement {
|
|
166
|
+
static getTag() {
|
|
167
|
+
return nestedTag;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
get defaults() {
|
|
171
|
+
return Object.assign({}, super.defaults, {
|
|
172
|
+
label: "rendered",
|
|
173
|
+
templates: {
|
|
174
|
+
main: `
|
|
175
|
+
<div id="level-one">
|
|
176
|
+
<div id="level-two">
|
|
177
|
+
<em id="deep-drop" data-monster-remove></em>
|
|
178
|
+
<b id="deep-keep"></b>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
`,
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
registerCustomElement(NestedRemoveComponent);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const element = document.createElement(nestedTag);
|
|
191
|
+
document.getElementById("test1").appendChild(element);
|
|
192
|
+
await settle();
|
|
193
|
+
|
|
194
|
+
expect(element.shadowRoot.querySelector("#deep-drop")).to.equal(null);
|
|
195
|
+
expect(element.shadowRoot.querySelector("#deep-keep")).to.not.equal(null);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
});
|