@schukai/monster 4.136.6 → 4.136.8

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.
@@ -73,8 +73,9 @@ describe("MessageStateButton", function () {
73
73
 
74
74
  describe("document.createElement", function () {
75
75
  it("should instance of message-state-button", function () {
76
- expect(document.createElement("monster-message-state-button")).is
77
- .instanceof(MessageStateButton);
76
+ expect(
77
+ document.createElement("monster-message-state-button"),
78
+ ).is.instanceof(MessageStateButton);
78
79
  });
79
80
  });
80
81
  });
@@ -93,9 +94,7 @@ describe("MessageStateButton", function () {
93
94
 
94
95
  setTimeout(() => {
95
96
  try {
96
- const inner = button.shadowRoot.querySelector(
97
- "monster-state-button",
98
- );
97
+ const inner = button.shadowRoot.querySelector("monster-state-button");
99
98
  expect(inner).to.exist;
100
99
 
101
100
  button.setAttribute("disabled", "");
@@ -231,19 +230,22 @@ describe("MessageStateButton", function () {
231
230
 
232
231
  setTimeout(() => {
233
232
  try {
234
- button.setMessage("<div><strong>Saved</strong><p>plain html</p></div>");
233
+ button.setMessage(
234
+ "<div><strong>Saved</strong><p>plain html</p></div>",
235
+ );
235
236
  button.showMessage();
236
237
 
237
238
  setTimeout(() => {
238
239
  try {
239
- const content = button.shadowRoot.querySelector('[part="content"]');
240
+ const content =
241
+ button.shadowRoot.querySelector('[part="content"]');
240
242
  const message = button.shadowRoot.querySelector(
241
243
  '[data-monster-role="message"]',
242
244
  );
243
245
  expect(content).to.exist;
244
- expect(content.getAttribute("data-monster-overflow-mode")).to.equal(
245
- "both",
246
- );
246
+ expect(
247
+ content.getAttribute("data-monster-overflow-mode"),
248
+ ).to.equal("both");
247
249
  expect(
248
250
  content.getAttribute("data-monster-message-layout"),
249
251
  ).to.equal("prose");
@@ -306,10 +308,7 @@ describe("MessageStateButton", function () {
306
308
  try {
307
309
  const wrapper = document.createElement("div");
308
310
  const line = document.createElement("div");
309
- line.setAttribute(
310
- "style",
311
- "white-space: nowrap; overflow-x: auto;",
312
- );
311
+ line.setAttribute("style", "white-space: nowrap; overflow-x: auto;");
313
312
  line.textContent =
314
313
  "this is intentionally a single long line to trigger wide layout";
315
314
  wrapper.appendChild(line);
@@ -319,14 +318,15 @@ describe("MessageStateButton", function () {
319
318
 
320
319
  setTimeout(() => {
321
320
  try {
322
- const content = button.shadowRoot.querySelector('[part="content"]');
321
+ const content =
322
+ button.shadowRoot.querySelector('[part="content"]');
323
323
  const message = button.shadowRoot.querySelector(
324
324
  '[data-monster-role="message"]',
325
325
  );
326
326
  expect(content).to.exist;
327
- expect(content.getAttribute("data-monster-overflow-mode")).to.equal(
328
- "both",
329
- );
327
+ expect(
328
+ content.getAttribute("data-monster-overflow-mode"),
329
+ ).to.equal("both");
330
330
  expect(
331
331
  content.getAttribute("data-monster-message-layout"),
332
332
  ).to.equal("wide");
@@ -351,7 +351,8 @@ describe("MessageStateButton", function () {
351
351
 
352
352
  beforeEach(() => {
353
353
  originalInnerWidth = window.innerWidth;
354
- originalGetBoundingClientRect = HTMLElement.prototype.getBoundingClientRect;
354
+ originalGetBoundingClientRect =
355
+ HTMLElement.prototype.getBoundingClientRect;
355
356
  });
356
357
 
357
358
  afterEach(() => {
@@ -360,7 +361,8 @@ describe("MessageStateButton", function () {
360
361
  writable: true,
361
362
  value: originalInnerWidth,
362
363
  });
363
- HTMLElement.prototype.getBoundingClientRect = originalGetBoundingClientRect;
364
+ HTMLElement.prototype.getBoundingClientRect =
365
+ originalGetBoundingClientRect;
364
366
  let mocks = document.getElementById("mocks");
365
367
  mocks.innerHTML = "";
366
368
  });
@@ -388,14 +390,7 @@ describe("MessageStateButton", function () {
388
390
  };
389
391
  }
390
392
 
391
- return {
392
- width: 100,
393
- height: 40,
394
- top: 0,
395
- left: 0,
396
- right: 100,
397
- bottom: 40,
398
- };
393
+ return originalGetBoundingClientRect.call(this);
399
394
  };
400
395
 
401
396
  setTimeout(() => {
@@ -405,18 +400,12 @@ describe("MessageStateButton", function () {
405
400
  );
406
401
  button.showMessage();
407
402
 
408
- setTimeout(() => {
409
- try {
410
- const popper = button.shadowRoot.querySelector(
411
- '[data-monster-role="popper"]',
412
- );
413
- expect(popper.style.width).to.equal("512px");
414
- expect(popper.style.maxWidth).to.equal("512px");
415
- done();
416
- } catch (e) {
417
- done(e);
418
- }
419
- }, 0);
403
+ const popper = button.shadowRoot.querySelector(
404
+ '[data-monster-role="popper"]',
405
+ );
406
+ expect(popper.style.width).to.equal("512px");
407
+ expect(popper.style.maxWidth).to.equal("512px");
408
+ done();
420
409
  } catch (e) {
421
410
  done(e);
422
411
  }
@@ -446,14 +435,7 @@ describe("MessageStateButton", function () {
446
435
  };
447
436
  }
448
437
 
449
- return {
450
- width: 100,
451
- height: 40,
452
- top: 0,
453
- left: 0,
454
- right: 100,
455
- bottom: 40,
456
- };
438
+ return originalGetBoundingClientRect.call(this);
457
439
  };
458
440
 
459
441
  setTimeout(() => {
@@ -465,18 +447,12 @@ describe("MessageStateButton", function () {
465
447
  button.setMessage(wrapper);
466
448
  button.showMessage();
467
449
 
468
- setTimeout(() => {
469
- try {
470
- const popper = button.shadowRoot.querySelector(
471
- '[data-monster-role="popper"]',
472
- );
473
- expect(popper.style.width).to.equal("768px");
474
- expect(popper.style.maxWidth).to.equal("768px");
475
- done();
476
- } catch (e) {
477
- done(e);
478
- }
479
- }, 0);
450
+ const popper = button.shadowRoot.querySelector(
451
+ '[data-monster-role="popper"]',
452
+ );
453
+ expect(popper.style.width).to.equal("768px");
454
+ expect(popper.style.maxWidth).to.equal("768px");
455
+ done();
480
456
  } catch (e) {
481
457
  done(e);
482
458
  }
@@ -47,7 +47,35 @@ function createJsonResponse(data, status = 200) {
47
47
  });
48
48
  }
49
49
 
50
+ function waitForCondition(check, {timeout = 4000, interval = 25} = {}) {
51
+ return new Promise((resolve, reject) => {
52
+ const start = Date.now();
53
+
54
+ const poll = () => {
55
+ try {
56
+ if (check()) {
57
+ resolve();
58
+ return;
59
+ }
60
+ } catch (e) {
61
+ reject(e);
62
+ return;
63
+ }
64
+
65
+ if (Date.now() - start >= timeout) {
66
+ reject(new Error('Timed out while waiting for test condition.'));
67
+ return;
68
+ }
69
+
70
+ setTimeout(poll, interval);
71
+ };
72
+
73
+ poll();
74
+ });
75
+ }
76
+
50
77
  let Select,
78
+ SelectStyleSheet,
51
79
  getDefaultSelectPopperPositionProfile,
52
80
  resolveSelectListDimension,
53
81
  resolveSelectPopperWidthConstraints,
@@ -75,6 +103,9 @@ describe('Select', function () {
75
103
  resolveSelectPopperWidthConstraints = m['resolveSelectPopperWidthConstraints'];
76
104
  resolveSelectVisibleRect = m['resolveSelectVisibleRect'];
77
105
  resolveSelectViewportMetrics = m['resolveSelectViewportMetrics'];
106
+ return import("../../../../source/components/form/stylesheet/select.mjs");
107
+ }).then((m) => {
108
+ SelectStyleSheet = m['SelectStyleSheet'];
78
109
  done()
79
110
  }).catch(e => done(e))
80
111
 
@@ -276,6 +307,16 @@ describe('Select', function () {
276
307
  expect(result.overflowY).to.equal('hidden');
277
308
  });
278
309
 
310
+ it('should keep option list scroll layout stable while overflow toggles', function () {
311
+ const cssText = Array.from(SelectStyleSheet.cssRules)
312
+ .map((rule) => rule.cssText)
313
+ .join('\n')
314
+ .replace(/\s+/g, '');
315
+
316
+ expect(cssText).to.contain('scrollbar-gutter:stable');
317
+ expect(cssText).to.not.contain('transition:height');
318
+ });
319
+
279
320
  it('should refresh the content max height when the available popper height grows again', function (done) {
280
321
  const mocks = document.getElementById('mocks');
281
322
  const select = document.createElement('monster-select');
@@ -810,7 +851,7 @@ describe('Select', function () {
810
851
  }, 50);
811
852
  });
812
853
 
813
- it('should reuse defaultOptionsUrl after clearing the filter and reopening', function (done) {
854
+ it('should reuse defaultOptionsUrl after clearing the filter and reopening', async function () {
814
855
  this.timeout(5000);
815
856
 
816
857
  let mocks = document.getElementById('mocks');
@@ -867,68 +908,33 @@ describe('Select', function () {
867
908
  }));
868
909
  };
869
910
 
870
- setTimeout(() => {
871
- let container;
872
- let filterInput;
873
-
874
- try {
875
- container = select.shadowRoot.querySelector('[data-monster-role=container]');
876
- filterInput = select.shadowRoot.querySelector('[data-monster-role=filter][name="popper-filter"]');
877
- container.click();
878
- } catch (e) {
879
- return done(e);
880
- }
881
-
882
- setTimeout(() => {
883
- try {
884
- expect(requests[0]).to.equal('https://example.com/defaults?page=1');
885
-
886
- filterInput.value = 'alpha';
887
- dispatchFilterKey(filterInput, 'KeyA', 'a');
888
- } catch (e) {
889
- return done(e);
890
- }
911
+ await waitForCondition(() => {
912
+ return select.shadowRoot.querySelector('[data-monster-role=container]') instanceof HTMLElement;
913
+ });
891
914
 
892
- setTimeout(() => {
893
- try {
894
- expect(requests[1]).to.equal('https://example.com/items?filter=alpha&page=1');
915
+ const container = select.shadowRoot.querySelector('[data-monster-role=container]');
916
+ const filterInput = select.shadowRoot.querySelector('[data-monster-role=filter][name="popper-filter"]');
895
917
 
896
- filterInput.value = '';
897
- dispatchFilterKey(filterInput, 'Backspace', 'Backspace');
898
- } catch (e) {
899
- return done(e);
900
- }
918
+ container.click();
919
+ await waitForCondition(() => requests.length >= 1);
920
+ expect(requests[0]).to.equal('https://example.com/defaults?page=1');
901
921
 
902
- setTimeout(() => {
903
- try {
904
- expect(requests[2]).to.equal('https://example.com/defaults?page=1');
922
+ filterInput.value = 'alpha';
923
+ dispatchFilterKey(filterInput, 'KeyA', 'a');
924
+ await waitForCondition(() => requests.length >= 2);
925
+ expect(requests[1]).to.equal('https://example.com/items?filter=alpha&page=1');
905
926
 
906
- container.click();
907
- } catch (e) {
908
- return done(e);
909
- }
927
+ filterInput.value = '';
928
+ dispatchFilterKey(filterInput, 'Backspace', 'Backspace');
929
+ await waitForCondition(() => requests.length >= 3);
930
+ expect(requests[2]).to.equal('https://example.com/defaults?page=1');
910
931
 
911
- setTimeout(() => {
912
- try {
913
- container.click();
914
- } catch (e) {
915
- return done(e);
916
- }
932
+ container.click();
933
+ await waitForCondition(() => select.shadowRoot.querySelector('[data-monster-role=popper]').style.display === 'none');
917
934
 
918
- setTimeout(() => {
919
- try {
920
- expect(requests[3]).to.equal('https://example.com/defaults?page=1');
921
- } catch (e) {
922
- return done(e);
923
- }
924
-
925
- done();
926
- }, 250);
927
- }, 50);
928
- }, 300);
929
- }, 300);
930
- }, 250);
931
- }, 50);
935
+ container.click();
936
+ await waitForCondition(() => requests.length >= 4);
937
+ expect(requests[3]).to.equal('https://example.com/defaults?page=1');
932
938
  });
933
939
 
934
940
  it('should keep unresolved lookup values visible and mark their badge', function (done) {
@@ -974,6 +980,52 @@ describe('Select', function () {
974
980
  }, 250);
975
981
  });
976
982
 
983
+ it('should keep unresolved object lookup values visible by key and mark their badge', function (done) {
984
+ this.timeout(3000);
985
+
986
+ let mocks = document.getElementById('mocks');
987
+ const requests = [];
988
+ global['fetch'] = function (url) {
989
+ requests.push(url.toString());
990
+ return createJsonResponse({
991
+ items: [],
992
+ pagination: {
993
+ total: 0,
994
+ page: 1,
995
+ perPage: 1
996
+ }
997
+ });
998
+ };
999
+
1000
+ const select = document.createElement('monster-select');
1001
+ select.setOption('lookup.url', 'https://example.com/items?filter={filter}');
1002
+ select.setOption('mapping.selector', 'items.*');
1003
+ select.setOption('mapping.labelTemplate', '${name}');
1004
+ select.setOption('mapping.valueTemplate', '${id}');
1005
+ select.setOption('selection', [{value: {id: 'missing-key'}}]);
1006
+ mocks.appendChild(select);
1007
+
1008
+ setTimeout(() => {
1009
+ try {
1010
+ const badge = select.shadowRoot.querySelector('[data-monster-role=badge]');
1011
+ const badgeLabel = select.shadowRoot.querySelector('[data-monster-role=badge-label]');
1012
+
1013
+ expect(requests).to.include('https://example.com/items?filter=missing-key');
1014
+ expect(badge).to.be.instanceof(HTMLDivElement);
1015
+ expect(badgeLabel).to.be.instanceof(HTMLDivElement);
1016
+ expect(badgeLabel.textContent.trim()).to.equal('missing-key');
1017
+ expect(badge.className).to.contain('monster-badge-warning');
1018
+ expect(badge.className).to.not.contain('monster-badge-primary');
1019
+ expect(badge.getAttribute('data-monster-unresolved')).to.equal('true');
1020
+ expect(badgeLabel.textContent).to.not.contain(select.getOption('labels.cannot-be-loaded'));
1021
+ } catch (e) {
1022
+ return done(e);
1023
+ }
1024
+
1025
+ done();
1026
+ }, 250);
1027
+ });
1028
+
977
1029
  it('should clear the unresolved badge state once a local option can resolve the value', function (done) {
978
1030
  this.timeout(4000);
979
1031
 
@@ -7,7 +7,7 @@ describe('Monster', function () {
7
7
  let monsterVersion
8
8
 
9
9
  /** don´t touch, replaced by make with package.json version */
10
- monsterVersion = new Version("4.128.3")
10
+ monsterVersion = new Version("4.136.7")
11
11
 
12
12
  let m = getMonsterVersion();
13
13
 
@@ -9,14 +9,18 @@ import "../cases/components/form/buy-box.mjs";
9
9
  import "../cases/components/form/message-state-button.mjs";
10
10
  import "../cases/components/form/button-bar.mjs";
11
11
  import "../cases/components/form/reload.mjs";
12
+ import "../cases/components/form/context-help.mjs";
12
13
  import "../cases/components/form/state-button.mjs";
13
14
  import "../cases/components/form/select.mjs";
14
15
  import "../cases/components/form/login.mjs";
15
16
  import "../cases/components/form/confirm-button.mjs";
17
+ import "../cases/components/form/context-error.mjs";
16
18
  import "../cases/components/form/form.mjs";
17
19
  import "../cases/components/form/tree-select.mjs";
20
+ import "../cases/components/form/popper-button.mjs";
18
21
  import "../cases/components/form/wizard.mjs";
19
22
  import "../cases/components/form/button.mjs";
23
+ import "../cases/components/form/floating-ui.mjs";
20
24
  import "../cases/components/form/toggle-switch.mjs";
21
25
  import "../cases/components/form/template.mjs";
22
26
  import "../cases/components/notify/message.mjs";
@@ -9,8 +9,8 @@
9
9
  </head>
10
10
  <body>
11
11
  <div id="headline" style="display: flex;align-items: center;justify-content: center;flex-direction: column;">
12
- <h1 style='margin-bottom: 0.1em;'>Monster 4.128.3</h1>
13
- <div id="lastupdate" style='font-size:0.7em'>last update So 5. Apr 19:19:33 CEST 2026</div>
12
+ <h1 style='margin-bottom: 0.1em;'>Monster 4.136.7</h1>
13
+ <div id="lastupdate" style='font-size:0.7em'>last update Wed Apr 22 21:49:31 CEST 2026</div>
14
14
  </div>
15
15
  <div id="mocha-errors"
16
16
  style="color: red;font-weight: bold;display: flex;align-items: center;justify-content: center;flex-direction: column;margin:20px;"></div>