@schukai/monster 4.128.2 → 4.128.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/package.json +1 -1
  2. package/source/components/content/stylesheet/camera-capture.mjs +1 -1
  3. package/source/components/content/stylesheet/copy.mjs +1 -1
  4. package/source/components/content/viewer/stylesheet/message.mjs +1 -1
  5. package/source/components/datatable/columnbar.mjs +30 -3
  6. package/source/components/datatable/datatable.mjs +26 -1
  7. package/source/components/datatable/stylesheet/filter-controls-defaults.mjs +1 -1
  8. package/source/components/form/stylesheet/button-bar.mjs +1 -1
  9. package/source/components/form/stylesheet/confirm-button.mjs +1 -1
  10. package/source/components/form/stylesheet/context-error.mjs +1 -1
  11. package/source/components/form/stylesheet/context-help.mjs +1 -1
  12. package/source/components/form/stylesheet/digits.mjs +1 -1
  13. package/source/components/form/stylesheet/field-set.mjs +1 -1
  14. package/source/components/form/stylesheet/login.mjs +1 -1
  15. package/source/components/form/stylesheet/popper-button.mjs +1 -1
  16. package/source/components/form/stylesheet/select.mjs +1 -1
  17. package/source/components/layout/stylesheet/popper.mjs +1 -1
  18. package/source/components/style/floating-ui.css +7 -0
  19. package/source/dom/customcontrol.mjs +1 -1
  20. package/source/dom/customelement.mjs +37 -15
  21. package/source/dom/updater.mjs +32 -12
  22. package/source/types/is.mjs +3 -0
  23. package/source/types/proxyobserver.mjs +10 -13
  24. package/source/types/version.mjs +1 -1
  25. package/test/cases/components/content/image-editor.mjs +98 -0
  26. package/test/cases/components/datatable/drag-scroll.mjs +218 -14
  27. package/test/cases/components/form/select.mjs +70 -32
  28. package/test/cases/dom/customcontrol.mjs +44 -10
  29. package/test/cases/dom/customelement-initfromscripthost.mjs +22 -1
  30. package/test/cases/dom/customelement.mjs +83 -0
  31. package/test/cases/dom/updater.mjs +98 -0
  32. package/test/cases/monster.mjs +1 -1
  33. package/test/cases/types/proxyobserver.mjs +31 -0
  34. package/test/web/import.js +3 -0
  35. package/test/web/puppeteer.mjs +94 -71
  36. package/test/web/test.html +29 -2
  37. package/test/web/tests.js +25120 -15937
@@ -88,6 +88,7 @@ const updaterRootSymbol = Symbol.for("@schukai/monster/dom/@@updater-root");
88
88
  const disposedSymbol = Symbol("disposed");
89
89
  const subjectObserverSymbol = Symbol("subjectObserver");
90
90
  const patchNodeKeySymbol = Symbol("patchNodeKey");
91
+ const queuedSnapshotSymbol = Symbol("queuedSnapshot");
91
92
 
92
93
  /**
93
94
  * The updater class connects an object with the DOM. In this way, structures and contents in the DOM can be
@@ -155,6 +156,7 @@ class Updater extends Base {
155
156
  this[pendingDiffsSymbol] = [];
156
157
  this[processingSymbol] = false;
157
158
  this[disposedSymbol] = false;
159
+ this[queuedSnapshotSymbol] = clone(this[internalSymbol].last);
158
160
 
159
161
  this[subjectObserverSymbol] = new Observer(() => {
160
162
  if (this[disposedSymbol] === true) {
@@ -162,12 +164,13 @@ class Updater extends Base {
162
164
  }
163
165
 
164
166
  const real = this[internalSymbol].subject.getRealSubject();
165
- const diffResult = diff(this[internalSymbol].last, real);
166
- this[internalSymbol].last = clone(real);
167
+ const diffResult = diff(this[queuedSnapshotSymbol], real);
167
168
  if (diffResult.length === 0) {
168
169
  return Promise.resolve();
169
170
  }
170
- this[pendingDiffsSymbol].push(diffResult);
171
+ const snapshot = clone(real);
172
+ this[queuedSnapshotSymbol] = snapshot;
173
+ this[pendingDiffsSymbol].push({ diffResult, snapshot });
171
174
  return this[processQueueSymbol]();
172
175
  });
173
176
  this[internalSymbol].subject.attachObserver(this[subjectObserverSymbol]);
@@ -194,7 +197,7 @@ class Updater extends Base {
194
197
  return Promise.resolve();
195
198
  }
196
199
 
197
- const diffResult = this[pendingDiffsSymbol].shift();
200
+ const { diffResult, snapshot } = this[pendingDiffsSymbol].shift();
198
201
  if (this[internalSymbol].features.batchUpdates === true) {
199
202
  const updatePaths = new Map();
200
203
  for (const change of Object.values(diffResult)) {
@@ -225,6 +228,7 @@ class Updater extends Base {
225
228
  await this[applyChangeSymbol](change);
226
229
  }
227
230
  }
231
+ this[internalSymbol].last = clone(snapshot);
228
232
  }
229
233
  } catch (err) {
230
234
  addErrorAttribute(this[internalSymbol]?.element, err);
@@ -371,6 +375,7 @@ class Updater extends Base {
371
375
  // the key __init__has no further meaning and is only
372
376
  // used to create the diff for empty objects.
373
377
  this[internalSymbol].last = { __init__: true };
378
+ this[queuedSnapshotSymbol] = clone(this[internalSymbol].last);
374
379
  return this[internalSymbol].subject.notifyObservers();
375
380
  }
376
381
 
@@ -597,12 +602,17 @@ function retrieveAndSetValue(element) {
597
602
  switch (type) {
598
603
  case "integer?":
599
604
  case "int?":
600
- case "number?":
605
+ case "number?": {
606
+ const empty =
607
+ value === undefined ||
608
+ value === null ||
609
+ (isString(value) && value.trim() === "");
601
610
  value = Number(value);
602
- if (isNaN(value) || 0 === value) {
611
+ if (empty || isNaN(value)) {
603
612
  value = undefined;
604
613
  }
605
614
  break;
615
+ }
606
616
 
607
617
  case "number":
608
618
  case "int":
@@ -1055,7 +1065,7 @@ function runUpdateContent(container, parts, subject) {
1055
1065
  * @type {HTMLElement}
1056
1066
  */
1057
1067
  for (const [element] of iterator.entries()) {
1058
- if (mem.has(element)) return;
1068
+ if (mem.has(element)) continue;
1059
1069
  mem.add(element);
1060
1070
 
1061
1071
  const attributes = element.getAttribute(ATTRIBUTE_UPDATER_REPLACE);
@@ -1113,7 +1123,7 @@ function runUpdatePatch(container, parts, subject) {
1113
1123
  }
1114
1124
 
1115
1125
  for (const [element] of iterator.entries()) {
1116
- if (mem.has(element)) return;
1126
+ if (mem.has(element)) continue;
1117
1127
  mem.add(element);
1118
1128
 
1119
1129
  const attributes = element.getAttribute(ATTRIBUTE_UPDATER_PATCH);
@@ -1501,7 +1511,7 @@ function runUpdateAttributes(container, parts, subject) {
1501
1511
  }
1502
1512
 
1503
1513
  for (const [element] of iterator.entries()) {
1504
- if (mem.has(element)) return;
1514
+ if (mem.has(element)) continue;
1505
1515
  mem.add(element);
1506
1516
 
1507
1517
  // this case occurs when the ATTRIBUTE_UPDATER_SELECT_THIS attribute is set
@@ -1576,7 +1586,7 @@ function runUpdateProperties(container, parts, subject) {
1576
1586
  }
1577
1587
 
1578
1588
  for (const [element] of iterator.entries()) {
1579
- if (mem.has(element)) return;
1589
+ if (mem.has(element)) continue;
1580
1590
  mem.add(element);
1581
1591
 
1582
1592
  // this case occurs when the ATTRIBUTE_UPDATER_SELECT_THIS attribute is set
@@ -1625,14 +1635,24 @@ function handleInputControlAttributeUpdate(element, name, value) {
1625
1635
  if (element instanceof HTMLSelectElement) {
1626
1636
  switch (element.type) {
1627
1637
  case "select-multiple":
1628
- for (const [index, opt] of Object.entries(element.options)) {
1629
- opt.selected = value.indexOf(opt.value) !== -1;
1638
+ const selectedValues = isArray(value)
1639
+ ? value
1640
+ : value === undefined || value === null
1641
+ ? []
1642
+ : [`${value}`];
1643
+ for (const [, opt] of Object.entries(element.options)) {
1644
+ opt.selected = selectedValues.indexOf(opt.value) !== -1;
1630
1645
  }
1631
1646
 
1632
1647
  break;
1633
1648
  case "select-one":
1634
1649
  // Only one value may be selected
1650
+ if (value === undefined || value === null) {
1651
+ element.selectedIndex = -1;
1652
+ break;
1653
+ }
1635
1654
 
1655
+ element.selectedIndex = -1;
1636
1656
  for (const [index, opt] of Object.entries(element.options)) {
1637
1657
  if (opt.value === value) {
1638
1658
  element.selectedIndex = index;
@@ -36,6 +36,9 @@ export {
36
36
  * @returns {boolean}
37
37
  */
38
38
  function isElement(value) {
39
+ if (typeof Element === "undefined") {
40
+ return false;
41
+ }
39
42
  return value instanceof Element;
40
43
  }
41
44
 
@@ -209,22 +209,19 @@ function getHandler() {
209
209
  return true;
210
210
  }
211
211
 
212
- let result;
213
- let descriptor = Reflect.getOwnPropertyDescriptor(target, key);
214
-
215
- if (descriptor === undefined) {
216
- descriptor = {
217
- writable: true,
218
- enumerable: true,
219
- configurable: true,
220
- };
212
+ const result = Reflect.set(target, key, value, receiver);
213
+ if (result !== true) {
214
+ return result;
221
215
  }
222
216
 
223
- descriptor["value"] = value;
224
- result = Reflect.defineProperty(target, key, descriptor);
225
-
226
217
  if (typeof key !== "symbol") {
227
- proxy.observers.notify(proxy);
218
+ let next = Reflect.get(target, key, receiver);
219
+ if (proxy.proxyMap.has(next)) {
220
+ next = proxy.proxyMap.get(next);
221
+ }
222
+ if (next !== current) {
223
+ proxy.observers.notify(proxy);
224
+ }
228
225
  }
229
226
 
230
227
  return result;
@@ -156,7 +156,7 @@ function getMonsterVersion() {
156
156
  }
157
157
 
158
158
  /** don't touch, replaced by make with package.json version */
159
- monsterVersion = new Version("4.125.0");
159
+ monsterVersion = new Version("4.128.2");
160
160
 
161
161
  return monsterVersion;
162
162
  }
@@ -0,0 +1,98 @@
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
+ describe('ImageEditor', function () {
12
+
13
+ let ImageEditor, document;
14
+ let registerCustomElement;
15
+ let CustomElement;
16
+ let assembleMethodSymbol;
17
+
18
+ before(function (done) {
19
+ initJSDOM({}).then(() => {
20
+ import("../../../../source/dom/customelement.mjs").then((domModule) => {
21
+ registerCustomElement = domModule['registerCustomElement'];
22
+ CustomElement = domModule['CustomElement'];
23
+ assembleMethodSymbol = domModule['assembleMethodSymbol'];
24
+ return import("../../../../source/components/content/image-editor.mjs");
25
+ }).then((m) => {
26
+ try {
27
+ ImageEditor = m['ImageEditor'];
28
+ document = getDocument();
29
+ done();
30
+ } catch (e) {
31
+ done(e);
32
+ }
33
+ }).catch((e) => done(e));
34
+ });
35
+ });
36
+
37
+ beforeEach(() => {
38
+ let mocks = document.getElementById('mocks');
39
+ mocks.innerHTML = '<div id="target"></div>';
40
+ });
41
+
42
+ afterEach(() => {
43
+ let mocks = document.getElementById('mocks');
44
+ mocks.innerHTML = '';
45
+ });
46
+
47
+ it('should not double-call attributeChangedCallback for readonly', function (done) {
48
+ const htmlTAG = 'monster-image-editor-observed-test';
49
+
50
+ class ObservedImageEditor extends ImageEditor {
51
+ static getTag() {
52
+ return htmlTAG;
53
+ }
54
+
55
+ get defaults() {
56
+ return Object.assign({}, super.defaults, {
57
+ shadowMode: false,
58
+ templates: {
59
+ main: '<div id="image-editor-test"></div>'
60
+ }
61
+ });
62
+ }
63
+
64
+ [assembleMethodSymbol]() {
65
+ return CustomElement.prototype[assembleMethodSymbol].call(this);
66
+ }
67
+
68
+ constructor() {
69
+ super();
70
+ this.attributeChangedCalls = [];
71
+ }
72
+
73
+ attributeChangedCallback(name, oldValue, newValue) {
74
+ this.attributeChangedCalls.push([name, oldValue, newValue]);
75
+ }
76
+ }
77
+
78
+ registerCustomElement(ObservedImageEditor);
79
+
80
+ let element = document.createElement(htmlTAG);
81
+ document.getElementById('target').appendChild(element);
82
+
83
+ element.attributeChangedCalls = [];
84
+ element.setAttribute('data-monster-readonly', '');
85
+
86
+ setTimeout(function () {
87
+ try {
88
+ const readonlyCalls = element.attributeChangedCalls.filter(
89
+ ([name]) => name === 'data-monster-readonly',
90
+ );
91
+ expect(readonlyCalls).to.have.length(1);
92
+ done();
93
+ } catch (e) {
94
+ done(e);
95
+ }
96
+ }, 20);
97
+ });
98
+ });
@@ -1,6 +1,7 @@
1
1
  import * as chai from "chai";
2
2
  import { chaiDom } from "../../../util/chai-dom.mjs";
3
3
  import { initJSDOM } from "../../../util/jsdom.mjs";
4
+ import { ResizeObserverMock } from "../../../util/resize-observer.mjs";
4
5
 
5
6
  const expect = chai.expect;
6
7
  chai.use(chaiDom);
@@ -32,6 +33,26 @@ function dispatchPointerEvent(target, type, options = {}) {
32
33
  return event;
33
34
  }
34
35
 
36
+ function mockScrollableElement(element, { clientWidth, scrollWidth, scrollLeft = 0 }) {
37
+ let currentScrollLeft = scrollLeft;
38
+
39
+ Object.defineProperty(element, "clientWidth", {
40
+ configurable: true,
41
+ value: clientWidth,
42
+ });
43
+ Object.defineProperty(element, "scrollWidth", {
44
+ configurable: true,
45
+ value: scrollWidth,
46
+ });
47
+ Object.defineProperty(element, "scrollLeft", {
48
+ configurable: true,
49
+ get: () => currentScrollLeft,
50
+ set: (value) => {
51
+ currentScrollLeft = value;
52
+ },
53
+ });
54
+ }
55
+
35
56
  describe("Datatable drag scroll", function () {
36
57
  before(async function () {
37
58
  await initJSDOM();
@@ -68,13 +89,9 @@ describe("Datatable drag scroll", function () {
68
89
  expect(scroll).to.exist;
69
90
  expect(cell).to.exist;
70
91
 
71
- Object.defineProperty(scroll, "clientWidth", {
72
- configurable: true,
73
- value: 200,
74
- });
75
- Object.defineProperty(scroll, "scrollWidth", {
76
- configurable: true,
77
- value: 600,
92
+ mockScrollableElement(scroll, {
93
+ clientWidth: 200,
94
+ scrollWidth: 600,
78
95
  });
79
96
 
80
97
  scroll.scrollLeft = 120;
@@ -134,13 +151,9 @@ describe("Datatable drag scroll", function () {
134
151
  expect(scroll).to.exist;
135
152
  expect(button).to.exist;
136
153
 
137
- Object.defineProperty(scroll, "clientWidth", {
138
- configurable: true,
139
- value: 200,
140
- });
141
- Object.defineProperty(scroll, "scrollWidth", {
142
- configurable: true,
143
- value: 600,
154
+ mockScrollableElement(scroll, {
155
+ clientWidth: 200,
156
+ scrollWidth: 600,
144
157
  });
145
158
 
146
159
  scroll.scrollLeft = 120;
@@ -255,4 +268,195 @@ describe("Datatable drag scroll", function () {
255
268
  await new Promise((resolve) => setTimeout(resolve, 0));
256
269
  expect(copied).deep.equal(['"Alpha";"Beta"']);
257
270
  });
271
+
272
+ it("defers resize observer grid updates to the next animation frame", async function () {
273
+ const OriginalResizeObserver = window.ResizeObserver;
274
+ const originalGlobalResizeObserver = globalThis.ResizeObserver;
275
+ const originalRequestAnimationFrame = window.requestAnimationFrame;
276
+ const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
277
+
278
+ class TrackingResizeObserver extends ResizeObserverMock {
279
+ static instances = [];
280
+
281
+ constructor(callback) {
282
+ super(callback);
283
+ TrackingResizeObserver.instances.push(this);
284
+ }
285
+ }
286
+
287
+ try {
288
+ window.ResizeObserver = TrackingResizeObserver;
289
+ globalThis.ResizeObserver = TrackingResizeObserver;
290
+
291
+ const mocks = document.getElementById("mocks");
292
+ const wrapper = document.createElement("div");
293
+ let wrapperWidth = 500;
294
+
295
+ Object.defineProperty(wrapper, "clientWidth", {
296
+ configurable: true,
297
+ get: () => wrapperWidth,
298
+ });
299
+
300
+ mocks.appendChild(wrapper);
301
+
302
+ const datatable = document.createElement("monster-datatable");
303
+ datatable.id = "resize-scheduled-table";
304
+ datatable.innerHTML = `
305
+ <template id="resize-scheduled-table-row">
306
+ <div data-monster-head="Name">Alpha</div>
307
+ <div data-monster-head="Value">Beta</div>
308
+ </template>
309
+ `;
310
+ datatable.setOption("responsive.breakpoint", 300);
311
+ datatable.setOption("data", [{}]);
312
+ wrapper.appendChild(datatable);
313
+
314
+ await new Promise((resolve) => setTimeout(resolve, 30));
315
+
316
+ const control = datatable.shadowRoot.querySelector(
317
+ "[data-monster-role=control]",
318
+ );
319
+ expect(control).to.exist;
320
+ expect(control.classList.contains("small")).to.equal(false);
321
+ expect(TrackingResizeObserver.instances).to.have.length.greaterThan(0);
322
+ const tableResizeObserver = TrackingResizeObserver.instances.find(
323
+ (observer) => observer.elements.includes(wrapper),
324
+ );
325
+ expect(tableResizeObserver).to.exist;
326
+
327
+ let scheduledCallback = null;
328
+ window.requestAnimationFrame = (callback) => {
329
+ scheduledCallback = callback;
330
+ return 1;
331
+ };
332
+ globalThis.requestAnimationFrame = window.requestAnimationFrame;
333
+
334
+ wrapperWidth = 200;
335
+ tableResizeObserver.triggerResize([]);
336
+
337
+ expect(scheduledCallback).to.be.a("function");
338
+ expect(control.classList.contains("small")).to.equal(false);
339
+
340
+ scheduledCallback();
341
+
342
+ expect(control.classList.contains("small")).to.equal(true);
343
+ } finally {
344
+ window.ResizeObserver = OriginalResizeObserver;
345
+ globalThis.ResizeObserver = originalGlobalResizeObserver;
346
+ window.requestAnimationFrame = originalRequestAnimationFrame;
347
+ globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
348
+ }
349
+ });
350
+
351
+ it("defers column bar resize updates to the next animation frame", async function () {
352
+ const OriginalResizeObserver = window.ResizeObserver;
353
+ const originalGlobalResizeObserver = globalThis.ResizeObserver;
354
+ const originalRequestAnimationFrame = window.requestAnimationFrame;
355
+ const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
356
+
357
+ class TrackingResizeObserver extends ResizeObserverMock {
358
+ static instances = [];
359
+
360
+ constructor(callback) {
361
+ super(callback);
362
+ TrackingResizeObserver.instances.push(this);
363
+ }
364
+ }
365
+
366
+ try {
367
+ window.ResizeObserver = TrackingResizeObserver;
368
+ globalThis.ResizeObserver = TrackingResizeObserver;
369
+
370
+ const mocks = document.getElementById("mocks");
371
+ const wrapper = document.createElement("div");
372
+ let parentWidth = 400;
373
+
374
+ Object.defineProperty(wrapper, "getBoundingClientRect", {
375
+ configurable: true,
376
+ value: () => ({ width: parentWidth }),
377
+ });
378
+
379
+ mocks.appendChild(wrapper);
380
+
381
+ const columnBar = document.createElement("monster-column-bar");
382
+ columnBar.setOption("columns", [
383
+ { index: 0, name: "One", visible: true },
384
+ { index: 1, name: "Two", visible: true },
385
+ { index: 2, name: "Three", visible: true },
386
+ { index: 3, name: "Four", visible: true },
387
+ { index: 4, name: "Five", visible: true },
388
+ { index: 5, name: "Six", visible: true },
389
+ ]);
390
+ wrapper.appendChild(columnBar);
391
+
392
+ await new Promise((resolve) => setTimeout(resolve, 30));
393
+
394
+ const control = columnBar.shadowRoot.querySelector(
395
+ "[data-monster-role=control]",
396
+ );
397
+ const settingsButton = columnBar.shadowRoot.querySelector(
398
+ "[data-monster-role=settings-button]",
399
+ );
400
+ const dotsContainer = columnBar.shadowRoot.querySelector(
401
+ "[data-monster-role=dots]",
402
+ );
403
+ const dots = Array.from(dotsContainer.querySelectorAll("li"));
404
+
405
+ expect(control).to.exist;
406
+ expect(settingsButton).to.exist;
407
+ expect(dots.length).to.equal(6);
408
+
409
+ Object.defineProperty(control, "getBoundingClientRect", {
410
+ configurable: true,
411
+ value: () => ({ width: parentWidth }),
412
+ });
413
+ Object.defineProperty(settingsButton, "getBoundingClientRect", {
414
+ configurable: true,
415
+ value: () => ({ width: 20 }),
416
+ });
417
+ dots.forEach((dot) => {
418
+ Object.defineProperty(dot, "getBoundingClientRect", {
419
+ configurable: true,
420
+ value: () => ({ width: 20 }),
421
+ });
422
+ });
423
+
424
+ await new Promise((resolve) => setTimeout(resolve, 0));
425
+
426
+ expect(
427
+ dotsContainer.querySelector(".dots-overflow-indicator"),
428
+ ).to.equal(null);
429
+
430
+ const columnBarResizeObserver = TrackingResizeObserver.instances.find(
431
+ (observer) => observer.elements.includes(control),
432
+ );
433
+ expect(columnBarResizeObserver).to.exist;
434
+
435
+ let scheduledCallback = null;
436
+ window.requestAnimationFrame = (callback) => {
437
+ scheduledCallback = callback;
438
+ return 1;
439
+ };
440
+ globalThis.requestAnimationFrame = window.requestAnimationFrame;
441
+
442
+ parentWidth = 80;
443
+ columnBarResizeObserver.triggerResize([]);
444
+
445
+ expect(scheduledCallback).to.be.a("function");
446
+ expect(
447
+ dotsContainer.querySelector(".dots-overflow-indicator"),
448
+ ).to.equal(null);
449
+
450
+ scheduledCallback();
451
+
452
+ expect(
453
+ dotsContainer.querySelector(".dots-overflow-indicator"),
454
+ ).to.exist;
455
+ } finally {
456
+ window.ResizeObserver = OriginalResizeObserver;
457
+ globalThis.ResizeObserver = originalGlobalResizeObserver;
458
+ window.requestAnimationFrame = originalRequestAnimationFrame;
459
+ globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
460
+ }
461
+ });
258
462
  });
@@ -238,7 +238,7 @@ describe('Select', function () {
238
238
  });
239
239
 
240
240
  it('should reset pagination and clear options on fetch error', function (done) {
241
- this.timeout(4000);
241
+ this.timeout(5000);
242
242
 
243
243
  let mocks = document.getElementById('mocks');
244
244
  const select = document.createElement('monster-select');
@@ -257,47 +257,85 @@ describe('Select', function () {
257
257
  const triggerFilter = (value) => {
258
258
  const filterInput = select.shadowRoot.querySelector('[data-monster-role=filter]');
259
259
  filterInput.value = value;
260
+ filterInput.dispatchEvent(new InputEvent('input', {
261
+ bubbles: true,
262
+ composed: true,
263
+ data: value
264
+ }));
260
265
  filterInput.dispatchEvent(new KeyboardEvent('keydown', {
261
266
  code: 'KeyA',
262
- bubbles: true
267
+ key: 'a',
268
+ bubbles: true,
269
+ composed: true
263
270
  }));
264
271
  };
265
272
 
266
- select.addEventListener('monster-options-set', () => {
267
- setTimeout(() => {
268
- try {
269
- const options = select.getOption('options');
270
- expect(options.length).to.equal(1);
271
- expect(pagination().getOption('currentPage')).to.equal(1);
272
- expect(pagination().getOption('pages')).to.equal(2);
273
- expect(pagination().getOption('objectsPerPage')).to.equal(1);
274
-
275
- triggerFilter('b');
276
-
277
- setTimeout(() => {
278
- try {
279
- const optionsAfterError = select.getOption('options');
280
- expect(optionsAfterError.length).to.equal(0);
281
- expect(pagination().getOption('currentPage')).to.equal(null);
282
- expect(pagination().getOption('pages')).to.equal(null);
283
- expect(pagination().getOption('objectsPerPage')).to.equal(null);
284
- expect(select.getOption('total')).to.equal(null);
285
- expect(select.getOption('messages.total')).to.equal("");
286
- } catch (e) {
287
- return done(e);
288
- }
273
+ const startedAt = Date.now();
274
+ const pollLoadedState = () => {
275
+ try {
276
+ const options = select.getOption('options');
277
+ const pager = pagination();
278
+
279
+ if (
280
+ options?.length !== 1 ||
281
+ !pager ||
282
+ pager.getOption('currentPage') !== 1 ||
283
+ pager.getOption('pages') !== 2 ||
284
+ pager.getOption('objectsPerPage') !== 1
285
+ ) {
286
+ if (Date.now() - startedAt < 3000) {
287
+ return setTimeout(pollLoadedState, 50);
288
+ }
289
+ }
289
290
 
290
- done();
291
- }, 500);
292
- } catch (e) {
293
- done(e);
291
+ expect(options.length).to.equal(1);
292
+ expect(pager.getOption('currentPage')).to.equal(1);
293
+ expect(pager.getOption('pages')).to.equal(2);
294
+ expect(pager.getOption('objectsPerPage')).to.equal(1);
295
+
296
+ triggerFilter('b');
297
+ setTimeout(pollErrorState, 50);
298
+ } catch (e) {
299
+ done(e);
300
+ }
301
+ };
302
+
303
+ const pollErrorState = () => {
304
+ try {
305
+ const optionsAfterError = select.getOption('options');
306
+ const pager = pagination();
307
+
308
+ if (
309
+ optionsAfterError?.length !== 0 ||
310
+ !pager ||
311
+ pager.getOption('currentPage') !== null ||
312
+ pager.getOption('pages') !== null ||
313
+ pager.getOption('objectsPerPage') !== null ||
314
+ select.getOption('total') !== null ||
315
+ select.getOption('messages.total') !== ""
316
+ ) {
317
+ if (Date.now() - startedAt < 4500) {
318
+ return setTimeout(pollErrorState, 50);
319
+ }
294
320
  }
295
- }, 50);
296
- }, {once: true});
321
+
322
+ expect(optionsAfterError.length).to.equal(0);
323
+ expect(pager.getOption('currentPage')).to.equal(null);
324
+ expect(pager.getOption('pages')).to.equal(null);
325
+ expect(pager.getOption('objectsPerPage')).to.equal(null);
326
+ expect(select.getOption('total')).to.equal(null);
327
+ expect(select.getOption('messages.total')).to.equal("");
328
+ } catch (e) {
329
+ return done(e);
330
+ }
331
+
332
+ done();
333
+ };
297
334
 
298
335
  setTimeout(() => {
299
336
  triggerFilter('a');
300
- }, 0);
337
+ setTimeout(pollLoadedState, 50);
338
+ }, 50);
301
339
  });
302
340
  });
303
341