@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
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import * as chai from "chai";
|
|
4
|
+
import { hasObjectLink } from "../../../source/dom/attributes.mjs";
|
|
5
|
+
import { customElementUpdaterLinkSymbol } from "../../../source/dom/constants.mjs";
|
|
6
|
+
import { getDocument } from "../../../source/dom/util.mjs";
|
|
7
|
+
import { chaiDom } from "../../util/chai-dom.mjs";
|
|
8
|
+
import { initJSDOM } from "../../util/jsdom.mjs";
|
|
9
|
+
|
|
10
|
+
let expect = chai.expect;
|
|
11
|
+
chai.use(chaiDom);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Regressions for issue #575.
|
|
15
|
+
*
|
|
16
|
+
* The base class allocated four MutationObservers, a coalescing timer and one
|
|
17
|
+
* Updater per child node, and released none of them on disconnect. These tests
|
|
18
|
+
* describe the released state and the rebind that has to restore it. The
|
|
19
|
+
* companion file customelement-rebind-baseline.mjs pins what must not break.
|
|
20
|
+
*/
|
|
21
|
+
describe("CustomElement teardown issue #575", function () {
|
|
22
|
+
let CustomElement;
|
|
23
|
+
let registerCustomElement;
|
|
24
|
+
let document;
|
|
25
|
+
|
|
26
|
+
const html = `
|
|
27
|
+
<div id="test1"></div>
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
before(function (done) {
|
|
31
|
+
initJSDOM({})
|
|
32
|
+
.then(() => import("../../../source/dom/customelement.mjs"))
|
|
33
|
+
.then((m) => {
|
|
34
|
+
try {
|
|
35
|
+
CustomElement = m["CustomElement"];
|
|
36
|
+
registerCustomElement = m["registerCustomElement"];
|
|
37
|
+
document = getDocument();
|
|
38
|
+
done();
|
|
39
|
+
} catch (e) {
|
|
40
|
+
done(e);
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
.catch(done);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
beforeEach(function () {
|
|
47
|
+
document.getElementById("mocks").innerHTML = html;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
afterEach(function () {
|
|
51
|
+
document.getElementById("mocks").innerHTML = "";
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
function settle(ms = 30) {
|
|
55
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function define(tag, build) {
|
|
59
|
+
if (!customElements.get(tag)) {
|
|
60
|
+
registerCustomElement(build(tag));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function linkedUpdaters(element) {
|
|
65
|
+
const found = [];
|
|
66
|
+
for (const group of element[customElementUpdaterLinkSymbol] ?? []) {
|
|
67
|
+
for (const updater of group) {
|
|
68
|
+
found.push(updater);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return found;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
describe("observers are released while disconnected", function () {
|
|
75
|
+
const TAG = "monster-teardown-attribute-575";
|
|
76
|
+
|
|
77
|
+
before(function () {
|
|
78
|
+
define(TAG, (tag) => {
|
|
79
|
+
return class extends CustomElement {
|
|
80
|
+
static getTag() {
|
|
81
|
+
return tag;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get defaults() {
|
|
85
|
+
return Object.assign({}, super.defaults, {
|
|
86
|
+
headline: "initial",
|
|
87
|
+
templates: {
|
|
88
|
+
main: '<span id="headline" data-monster-replace="path:headline"></span>',
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should not apply an option attribute change while disconnected", async function () {
|
|
97
|
+
const element = document.createElement(TAG);
|
|
98
|
+
document.getElementById("test1").appendChild(element);
|
|
99
|
+
await settle();
|
|
100
|
+
|
|
101
|
+
expect(element.getOption("headline")).to.equal("initial");
|
|
102
|
+
|
|
103
|
+
element.remove();
|
|
104
|
+
await settle();
|
|
105
|
+
|
|
106
|
+
element.setAttribute("data-monster-option-headline", "while-detached");
|
|
107
|
+
await settle();
|
|
108
|
+
|
|
109
|
+
expect(element.getOption("headline")).to.equal("initial");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("should apply option attributes again after reconnecting", async function () {
|
|
113
|
+
const element = document.createElement(TAG);
|
|
114
|
+
const parent = document.getElementById("test1");
|
|
115
|
+
parent.appendChild(element);
|
|
116
|
+
await settle();
|
|
117
|
+
|
|
118
|
+
element.remove();
|
|
119
|
+
await settle();
|
|
120
|
+
element.setAttribute("data-monster-option-headline", "while-detached");
|
|
121
|
+
await settle();
|
|
122
|
+
|
|
123
|
+
parent.appendChild(element);
|
|
124
|
+
await settle();
|
|
125
|
+
|
|
126
|
+
// the attribute set while detached is picked up by the rebind
|
|
127
|
+
expect(element.getOption("headline")).to.equal("while-detached");
|
|
128
|
+
expect(element.shadowRoot.querySelector("#headline").textContent).to.equal(
|
|
129
|
+
"while-detached",
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// and live attribute changes work again
|
|
133
|
+
element.setAttribute("data-monster-option-headline", "after-reconnect");
|
|
134
|
+
await settle();
|
|
135
|
+
|
|
136
|
+
expect(element.getOption("headline")).to.equal("after-reconnect");
|
|
137
|
+
expect(element.shadowRoot.querySelector("#headline").textContent).to.equal(
|
|
138
|
+
"after-reconnect",
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("updaters are disposed on disconnect", function () {
|
|
144
|
+
const TAG = "monster-teardown-updater-575";
|
|
145
|
+
|
|
146
|
+
before(function () {
|
|
147
|
+
define(TAG, (tag) => {
|
|
148
|
+
return class extends CustomElement {
|
|
149
|
+
static getTag() {
|
|
150
|
+
return tag;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
get defaults() {
|
|
154
|
+
return Object.assign({}, super.defaults, {
|
|
155
|
+
value: "a",
|
|
156
|
+
templates: {
|
|
157
|
+
main: '<span id="value" data-monster-replace="path:value"></span>',
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("should dispose every linked updater and drop the object link", async function () {
|
|
166
|
+
const element = document.createElement(TAG);
|
|
167
|
+
document.getElementById("test1").appendChild(element);
|
|
168
|
+
await settle();
|
|
169
|
+
|
|
170
|
+
const updaters = linkedUpdaters(element);
|
|
171
|
+
expect(updaters.length).to.be.greaterThan(0);
|
|
172
|
+
for (const updater of updaters) {
|
|
173
|
+
expect(updater.isDisposed()).to.be.false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
element.remove();
|
|
177
|
+
await settle();
|
|
178
|
+
|
|
179
|
+
for (const updater of updaters) {
|
|
180
|
+
expect(updater.isDisposed()).to.be.true;
|
|
181
|
+
}
|
|
182
|
+
expect(hasObjectLink(element, customElementUpdaterLinkSymbol)).to.be.false;
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should link fresh, working updaters after reconnecting", async function () {
|
|
186
|
+
const element = document.createElement(TAG);
|
|
187
|
+
const parent = document.getElementById("test1");
|
|
188
|
+
parent.appendChild(element);
|
|
189
|
+
await settle();
|
|
190
|
+
|
|
191
|
+
const before = linkedUpdaters(element);
|
|
192
|
+
|
|
193
|
+
element.remove();
|
|
194
|
+
await settle();
|
|
195
|
+
parent.appendChild(element);
|
|
196
|
+
await settle();
|
|
197
|
+
|
|
198
|
+
const after = linkedUpdaters(element);
|
|
199
|
+
expect(after.length).to.equal(before.length);
|
|
200
|
+
for (const updater of after) {
|
|
201
|
+
expect(updater.isDisposed()).to.be.false;
|
|
202
|
+
}
|
|
203
|
+
for (const updater of before) {
|
|
204
|
+
expect(after).to.not.include(updater);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
element.setOption("value", "b");
|
|
208
|
+
await settle();
|
|
209
|
+
expect(element.shadowRoot.querySelector("#value").textContent).to.equal(
|
|
210
|
+
"b",
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe("registerTeardown", function () {
|
|
216
|
+
const TAG = "monster-teardown-registry-575";
|
|
217
|
+
|
|
218
|
+
before(function () {
|
|
219
|
+
define(TAG, (tag) => {
|
|
220
|
+
return class extends CustomElement {
|
|
221
|
+
static getTag() {
|
|
222
|
+
return tag;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
get defaults() {
|
|
226
|
+
return Object.assign({}, super.defaults, {
|
|
227
|
+
templates: { main: "<span>registry</span>" },
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("should run a registered teardown once per disconnect", async function () {
|
|
235
|
+
const element = document.createElement(TAG);
|
|
236
|
+
const parent = document.getElementById("test1");
|
|
237
|
+
parent.appendChild(element);
|
|
238
|
+
await settle();
|
|
239
|
+
|
|
240
|
+
let calls = 0;
|
|
241
|
+
element.registerTeardown(() => {
|
|
242
|
+
calls++;
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
expect(calls).to.equal(0);
|
|
246
|
+
|
|
247
|
+
element.remove();
|
|
248
|
+
await settle();
|
|
249
|
+
expect(calls).to.equal(1);
|
|
250
|
+
|
|
251
|
+
parent.appendChild(element);
|
|
252
|
+
await settle();
|
|
253
|
+
expect(calls).to.equal(1);
|
|
254
|
+
|
|
255
|
+
element.remove();
|
|
256
|
+
await settle();
|
|
257
|
+
expect(calls).to.equal(1);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it("should keep tearing down when one callback throws", async function () {
|
|
261
|
+
const element = document.createElement(TAG);
|
|
262
|
+
document.getElementById("test1").appendChild(element);
|
|
263
|
+
await settle();
|
|
264
|
+
|
|
265
|
+
let second = 0;
|
|
266
|
+
element.registerTeardown(() => {
|
|
267
|
+
throw new Error("teardown failure");
|
|
268
|
+
});
|
|
269
|
+
element.registerTeardown(() => {
|
|
270
|
+
second++;
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
element.remove();
|
|
274
|
+
await settle();
|
|
275
|
+
|
|
276
|
+
expect(second).to.equal(1);
|
|
277
|
+
expect(hasObjectLink(element, customElementUpdaterLinkSymbol)).to.be.false;
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe("one time assembly steps", function () {
|
|
282
|
+
const TAG = "monster-teardown-once-575";
|
|
283
|
+
|
|
284
|
+
before(function () {
|
|
285
|
+
define(TAG, (tag) => {
|
|
286
|
+
return class extends CustomElement {
|
|
287
|
+
static getTag() {
|
|
288
|
+
return tag;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
get defaults() {
|
|
292
|
+
return Object.assign({}, super.defaults, {
|
|
293
|
+
templates: { main: "<span>once</span>" },
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
initCustomControlCallback() {
|
|
298
|
+
this.initCalls = (this.initCalls || 0) + 1;
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("should run the init callback exactly once across two connect cycles", async function () {
|
|
305
|
+
const element = document.createElement(TAG);
|
|
306
|
+
const parent = document.getElementById("test1");
|
|
307
|
+
parent.appendChild(element);
|
|
308
|
+
await settle();
|
|
309
|
+
|
|
310
|
+
expect(element.initCalls).to.equal(1);
|
|
311
|
+
|
|
312
|
+
element.remove();
|
|
313
|
+
await settle();
|
|
314
|
+
parent.appendChild(element);
|
|
315
|
+
await settle();
|
|
316
|
+
|
|
317
|
+
expect(element.initCalls).to.equal(1);
|
|
318
|
+
expect(element.hasAttribute("data-monster-error")).to.be.false;
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
describe("derived classes without an updater link", function () {
|
|
323
|
+
|
|
324
|
+
before(async function () {
|
|
325
|
+
await import("../../../source/components/form/select-link.mjs");
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// monster-select-link renders no updater bound markup, so it never had an
|
|
329
|
+
// object link and used to be reassembled on every connect. It also did not
|
|
330
|
+
// call super.disconnectedCallback() at all.
|
|
331
|
+
it("should run the base teardown for monster-select-link", async function () {
|
|
332
|
+
const element = document.createElement("monster-select-link");
|
|
333
|
+
document.getElementById("test1").appendChild(element);
|
|
334
|
+
await settle();
|
|
335
|
+
|
|
336
|
+
let teardownCalls = 0;
|
|
337
|
+
element.registerTeardown(() => {
|
|
338
|
+
teardownCalls++;
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
element.remove();
|
|
342
|
+
await settle();
|
|
343
|
+
|
|
344
|
+
expect(teardownCalls).to.equal(1);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it("should survive a disconnect and reconnect cycle", async function () {
|
|
348
|
+
const parent = document.getElementById("test1");
|
|
349
|
+
|
|
350
|
+
const element = document.createElement("monster-select-link");
|
|
351
|
+
element.setAttribute("data-monster-option-param", "id");
|
|
352
|
+
parent.appendChild(element);
|
|
353
|
+
await settle();
|
|
354
|
+
|
|
355
|
+
expect(element.hasAttribute("data-monster-error")).to.be.false;
|
|
356
|
+
|
|
357
|
+
element.remove();
|
|
358
|
+
await settle();
|
|
359
|
+
parent.appendChild(element);
|
|
360
|
+
await settle();
|
|
361
|
+
|
|
362
|
+
// no second assembly, no error, and the option state is still there
|
|
363
|
+
expect(element.hasAttribute("data-monster-error")).to.be.false;
|
|
364
|
+
expect(element.getOption("param")).to.equal("id");
|
|
365
|
+
expect(element.getOption("clearSelection")).to.equal(true);
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
describe("child mutation observer", function () {
|
|
370
|
+
const TAG = "monster-teardown-child-observer-575";
|
|
371
|
+
|
|
372
|
+
before(function () {
|
|
373
|
+
define(TAG, (tag) => {
|
|
374
|
+
return class extends CustomElement {
|
|
375
|
+
static getTag() {
|
|
376
|
+
return tag;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
get defaults() {
|
|
380
|
+
return Object.assign({}, super.defaults, {
|
|
381
|
+
features: { mutationObserver: true },
|
|
382
|
+
templates: { main: "<slot></slot>" },
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it("should not run updaters for children added while disconnected", async function () {
|
|
390
|
+
const element = document.createElement(TAG);
|
|
391
|
+
document.getElementById("test1").appendChild(element);
|
|
392
|
+
await settle();
|
|
393
|
+
|
|
394
|
+
const updaters = linkedUpdaters(element);
|
|
395
|
+
element.remove();
|
|
396
|
+
|
|
397
|
+
// the coalescing timer is 50ms, so a child added right after the remove
|
|
398
|
+
// would still fire it
|
|
399
|
+
element.appendChild(document.createElement("span"));
|
|
400
|
+
await settle(120);
|
|
401
|
+
|
|
402
|
+
for (const updater of updaters) {
|
|
403
|
+
expect(updater.isDisposed()).to.be.true;
|
|
404
|
+
}
|
|
405
|
+
expect(element.hasAttribute("data-monster-error")).to.be.false;
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
});
|