@schukai/monster 4.136.7 → 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.
package/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.136.7"}
1
+ {"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.136.8"}
@@ -2238,22 +2238,56 @@ function parseSlotsToOptions() {
2238
2238
  function buildSelectionLabel(value) {
2239
2239
  const strict = this.getOption("features.useStrictValueComparison") === true;
2240
2240
  const map = this[optionsMapSymbol];
2241
- const key = strict ? value : String(value);
2241
+ const key = strict ? value : getSelectionStateKey.call(this, value);
2242
2242
 
2243
2243
  if (map && map.has(key)) {
2244
2244
  if (clearUnresolvedSelectionValue.call(this, value)) {
2245
- this[lookupCacheSymbol].delete(value);
2245
+ this[lookupCacheSymbol].delete(getSelectionCacheKey.call(this, value));
2246
2246
  }
2247
2247
  return map.get(key);
2248
2248
  }
2249
2249
 
2250
- if (this[lookupCacheSymbol].has(value)) {
2251
- return this[lookupCacheSymbol].get(value);
2250
+ const cacheKey = getSelectionCacheKey.call(this, value);
2251
+ if (this[lookupCacheSymbol].has(cacheKey)) {
2252
+ return this[lookupCacheSymbol].get(cacheKey);
2252
2253
  }
2253
2254
 
2254
2255
  return undefined;
2255
2256
  }
2256
2257
 
2258
+ /**
2259
+ * @private
2260
+ * @param {*} value
2261
+ * @returns {string}
2262
+ */
2263
+ function getSelectionValueLabel(value) {
2264
+ if (isValueIsEmpty.call(this, value)) {
2265
+ return "";
2266
+ }
2267
+
2268
+ if (isPrimitive(value)) {
2269
+ return String(value);
2270
+ }
2271
+
2272
+ if (isObject(value)) {
2273
+ for (const key of ["value", "id", "key"]) {
2274
+ const candidate = value?.[key];
2275
+ if (!isValueIsEmpty.call(this, candidate) && isPrimitive(candidate)) {
2276
+ return String(candidate);
2277
+ }
2278
+ }
2279
+
2280
+ try {
2281
+ const json = JSON.stringify(value);
2282
+ if (isString(json)) {
2283
+ return json;
2284
+ }
2285
+ } catch (e) {}
2286
+ }
2287
+
2288
+ return String(value);
2289
+ }
2290
+
2257
2291
  /**
2258
2292
  * @private
2259
2293
  * @param {*} value
@@ -2262,7 +2296,33 @@ function buildSelectionLabel(value) {
2262
2296
  function getSelectionStateKey(value) {
2263
2297
  return this.getOption("features.useStrictValueComparison") === true
2264
2298
  ? value
2265
- : String(value);
2299
+ : getSelectionValueLabel.call(this, value);
2300
+ }
2301
+
2302
+ /**
2303
+ * @private
2304
+ * @param {*} value
2305
+ * @returns {*}
2306
+ */
2307
+ function getSelectionCacheKey(value) {
2308
+ return getSelectionStateKey.call(this, value);
2309
+ }
2310
+
2311
+ /**
2312
+ * @private
2313
+ * @param {*} current
2314
+ * @param {*} expected
2315
+ * @returns {boolean}
2316
+ */
2317
+ function areLookupValuesEqual(current, expected) {
2318
+ if (this.getOption("features.useStrictValueComparison") === true) {
2319
+ return current === expected;
2320
+ }
2321
+
2322
+ return (
2323
+ getSelectionValueLabel.call(this, current) ===
2324
+ getSelectionValueLabel.call(this, expected)
2325
+ );
2266
2326
  }
2267
2327
 
2268
2328
  /**
@@ -2357,6 +2417,8 @@ function buildSelectionItem(value, preferredLabel) {
2357
2417
  */
2358
2418
  async function lookupValueAndCache(value) {
2359
2419
  const lookupUrl = this.getOption("lookup.url");
2420
+ const lookupValue = getSelectionValueLabel.call(this, value);
2421
+ const cacheKey = getSelectionCacheKey.call(this, value);
2360
2422
  // A more robust check for invalid values. 0 and "" are valid.
2361
2423
  if (
2362
2424
  !lookupUrl ||
@@ -2381,7 +2443,10 @@ async function lookupValueAndCache(value) {
2381
2443
 
2382
2444
  const markerOpen = this.getOption("filter.marker.open", "{");
2383
2445
  const markerClose = this.getOption("filter.marker.close", "}");
2384
- const url = lookupUrl.replace(`${markerOpen}filter${markerClose}`, value);
2446
+ const url = lookupUrl.replace(
2447
+ `${markerOpen}filter${markerClose}`,
2448
+ lookupValue,
2449
+ );
2385
2450
 
2386
2451
  const data = await fetchData.call(this, url);
2387
2452
 
@@ -2407,8 +2472,9 @@ async function lookupValueAndCache(value) {
2407
2472
  }
2408
2473
 
2409
2474
  // The lookup might return more than the requested value, so we cache all of them.
2410
- if (this[lookupCacheSymbol].get(itemValue) !== itemLabel) {
2411
- this[lookupCacheSymbol].set(itemValue, itemLabel);
2475
+ const itemCacheKey = getSelectionCacheKey.call(this, itemValue);
2476
+ if (this[lookupCacheSymbol].get(itemCacheKey) !== itemLabel) {
2477
+ this[lookupCacheSymbol].set(itemCacheKey, itemLabel);
2412
2478
  refreshSelection = true;
2413
2479
  }
2414
2480
 
@@ -2416,13 +2482,13 @@ async function lookupValueAndCache(value) {
2416
2482
  refreshSelection = true;
2417
2483
  }
2418
2484
 
2419
- if (`${itemValue}` === `${value}`) {
2485
+ if (areLookupValuesEqual.call(this, itemValue, value)) {
2420
2486
  found = true;
2421
2487
  }
2422
2488
  }
2423
2489
 
2424
- if (!found && !this[lookupCacheSymbol].has(value)) {
2425
- this[lookupCacheSymbol].set(value, `${value}`);
2490
+ if (!found && !this[lookupCacheSymbol].has(cacheKey)) {
2491
+ this[lookupCacheSymbol].set(cacheKey, lookupValue);
2426
2492
  refreshSelection = true;
2427
2493
  }
2428
2494
 
@@ -2433,8 +2499,8 @@ async function lookupValueAndCache(value) {
2433
2499
  hasError = true;
2434
2500
  addErrorAttribute(this, e);
2435
2501
 
2436
- if (!this[lookupCacheSymbol].has(value)) {
2437
- this[lookupCacheSymbol].set(value, `${value}`);
2502
+ if (!this[lookupCacheSymbol].has(cacheKey)) {
2503
+ this[lookupCacheSymbol].set(cacheKey, lookupValue);
2438
2504
  refreshSelection = true;
2439
2505
  }
2440
2506
 
@@ -2482,11 +2548,7 @@ function getSelectionLabel(value) {
2482
2548
  });
2483
2549
  }
2484
2550
 
2485
- if (isString(value) || isInteger(value)) {
2486
- return `${value}`;
2487
- }
2488
-
2489
- return this.getOption("labels.cannot-be-loaded", value);
2551
+ return getSelectionValueLabel.call(this, value);
2490
2552
  }
2491
2553
 
2492
2554
  /**
@@ -2556,17 +2618,15 @@ function initOptionObserver() {
2556
2618
  if (isArray(options)) {
2557
2619
  for (const o of options) {
2558
2620
  if (isPrimitive(o)) {
2559
- const key = strict ? o : String(o);
2621
+ const key = getSelectionStateKey.call(self, o);
2560
2622
  if (!map.has(key)) {
2561
2623
  map.set(key, o);
2562
2624
  }
2563
2625
  } else if (isObject(o)) {
2564
2626
  const v = o?.value;
2565
2627
  const l = o?.label;
2566
- // If strict is true, use value as is (could be number, symbol, etc.)
2567
- // If strict is false, use String(value) to allow loose matching
2568
2628
  if (v !== undefined) {
2569
- const key = strict ? v : String(v);
2629
+ const key = getSelectionStateKey.call(self, v);
2570
2630
  if (!map.has(key)) {
2571
2631
  map.set(key, l);
2572
2632
  }
@@ -219,15 +219,13 @@ function buildFloatingMiddleware(
219
219
  const kv = line.split(":");
220
220
  const fn = kv.shift();
221
221
 
222
- switch (fn) {
223
- case "flip":
224
- result[key] = flip(detectOverflowOptions);
225
- break;
226
- case "shift":
227
- result[key] = shift(
228
- normalizeShiftOptions(kv, detectOverflowOptions),
229
- );
230
- break;
222
+ switch (fn) {
223
+ case "flip":
224
+ result[key] = flip(detectOverflowOptions);
225
+ break;
226
+ case "shift":
227
+ result[key] = shift(normalizeShiftOptions(kv, detectOverflowOptions));
228
+ break;
231
229
  case "autoPlacement":
232
230
  let defaultAllowedPlacements = ["top", "bottom", "left", "right"];
233
231
 
@@ -922,7 +920,10 @@ function startAutoUpdate(controlElement, popperElement, callback) {
922
920
  stopAutoUpdate(popperElement);
923
921
  autoUpdateCleanupMap.set(
924
922
  popperElement,
925
- autoUpdate(controlElement, popperElement, callback),
923
+ autoUpdate(controlElement, popperElement, callback, {
924
+ elementResize: typeof ResizeObserver === "function",
925
+ layoutShift: false,
926
+ }),
926
927
  );
927
928
  }
928
929
 
@@ -939,6 +940,10 @@ function stopAutoUpdate(popperElement) {
939
940
  function startFloatingResizeObserver(controlElement, popperElement, config) {
940
941
  stopFloatingResizeObserver(popperElement);
941
942
 
943
+ if (typeof ResizeObserver !== "function") {
944
+ return;
945
+ }
946
+
942
947
  const observer = new ResizeObserver(() => {
943
948
  scheduleSettlingPass(controlElement, popperElement, config);
944
949
  });
@@ -949,7 +954,10 @@ function startFloatingResizeObserver(controlElement, popperElement, config) {
949
954
 
950
955
  function stopFloatingResizeObserver(popperElement) {
951
956
  const observer = floatingResizeObserverMap.get(popperElement);
952
- if (observer instanceof ResizeObserver) {
957
+ if (
958
+ typeof ResizeObserver === "function" &&
959
+ observer instanceof ResizeObserver
960
+ ) {
953
961
  observer.disconnect();
954
962
  }
955
963
  floatingResizeObserverMap.delete(popperElement);
@@ -180,6 +180,10 @@ function syncAutoUpdate(instance) {
180
180
  () => {
181
181
  void instance.update();
182
182
  },
183
+ {
184
+ elementResize: typeof ResizeObserver === "function",
185
+ layoutShift: false,
186
+ },
183
187
  );
184
188
  }
185
189
 
@@ -44,6 +44,13 @@ const activeSubmenuHiderSymbol = Symbol("activeSubmenuHider");
44
44
  const hideHamburgerMenuSymbol = Symbol("hideHamburgerMenu");
45
45
  const hamburgerCloseButtonSymbol = Symbol("hamburgerCloseButton");
46
46
 
47
+ function getAutoUpdateOptions() {
48
+ return {
49
+ elementResize: typeof ResizeObserver === "function",
50
+ layoutShift: false,
51
+ };
52
+ }
53
+
47
54
  /**
48
55
  * A responsive site navigation that automatically moves menu items into a hamburger menu
49
56
  * when there isn't enough available space.
@@ -215,39 +222,48 @@ function initEventHandler() {
215
222
 
216
223
  hamburgerNav.scrollIntoView({ block: "start", behavior: "smooth" });
217
224
 
218
- cleanup = autoUpdate(hamburgerButton, hamburgerNav, () => {
219
- if (window.innerWidth > 768) {
220
- const strategy = getBestPositionStrategy(this);
221
-
222
- computePosition(hamburgerButton, hamburgerNav, {
223
- placement: "bottom-end",
224
- strategy: strategy,
225
- middleware: [
226
- offset(8),
227
- flip(),
228
- shift({ padding: 8 }),
229
- size({
230
- apply: ({ availableHeight, elements }) => {
231
- Object.assign(elements.floating.style, {
232
- maxHeight: `${availableHeight}px`,
233
- overflowY: "auto",
234
- });
235
- },
236
- padding: 8,
237
- }),
238
- ],
239
- }).then(({ x, y, strategy }) => {
225
+ cleanup = autoUpdate(
226
+ hamburgerButton,
227
+ hamburgerNav,
228
+ () => {
229
+ if (window.innerWidth > 768) {
230
+ const strategy = getBestPositionStrategy(this);
231
+
232
+ computePosition(hamburgerButton, hamburgerNav, {
233
+ placement: "bottom-end",
234
+ strategy: strategy,
235
+ middleware: [
236
+ offset(8),
237
+ flip(),
238
+ shift({ padding: 8 }),
239
+ size({
240
+ apply: ({ availableHeight, elements }) => {
241
+ Object.assign(elements.floating.style, {
242
+ maxHeight: `${availableHeight}px`,
243
+ overflowY: "auto",
244
+ });
245
+ },
246
+ padding: 8,
247
+ }),
248
+ ],
249
+ }).then(({ x, y, strategy }) => {
250
+ Object.assign(hamburgerNav.style, {
251
+ position: strategy,
252
+ left: `${x}px`,
253
+ top: `${y}px`,
254
+ });
255
+ });
256
+ } else {
257
+ // Mobile view (fullscreen overlay), position is handled by CSS
240
258
  Object.assign(hamburgerNav.style, {
241
- position: strategy,
242
- left: `${x}px`,
243
- top: `${y}px`,
259
+ position: "",
260
+ left: "",
261
+ top: "",
244
262
  });
245
- });
246
- } else {
247
- // Mobile view (fullscreen overlay), position is handled by CSS
248
- Object.assign(hamburgerNav.style, { position: "", left: "", top: "" });
249
- }
250
- });
263
+ }
264
+ },
265
+ getAutoUpdateOptions(),
266
+ );
251
267
  setTimeout(() => document.addEventListener("click", handleOutsideClick), 0);
252
268
  };
253
269
 
@@ -395,35 +411,40 @@ function setupSubmenu(parentLi, context = "visible", level = 1) {
395
411
  level,
396
412
  });
397
413
  if (!cleanup) {
398
- cleanup = autoUpdate(parentLi, submenu, () => {
399
- const middleware = [offset(8), flip(), shift({ padding: 8 })];
400
- const containsSubmenus = submenu.querySelector(
401
- "ul, div[part='mega-menu']",
402
- );
403
- if (!containsSubmenus) {
404
- middleware.push(
405
- size({
406
- apply: ({ availableHeight, elements }) => {
407
- Object.assign(elements.floating.style, {
408
- maxHeight: `${availableHeight}px`,
409
- overflowY: "auto",
410
- });
411
- },
412
- padding: 8,
413
- }),
414
+ cleanup = autoUpdate(
415
+ parentLi,
416
+ submenu,
417
+ () => {
418
+ const middleware = [offset(8), flip(), shift({ padding: 8 })];
419
+ const containsSubmenus = submenu.querySelector(
420
+ "ul, div[part='mega-menu']",
414
421
  );
415
- }
416
- computePosition(parentLi, submenu, {
417
- placement: level === 1 ? "bottom-start" : "right-start",
418
- middleware: middleware,
419
- }).then(({ x, y, strategy }) => {
420
- Object.assign(submenu.style, {
421
- position: strategy,
422
- left: `${x}px`,
423
- top: `${y}px`,
422
+ if (!containsSubmenus) {
423
+ middleware.push(
424
+ size({
425
+ apply: ({ availableHeight, elements }) => {
426
+ Object.assign(elements.floating.style, {
427
+ maxHeight: `${availableHeight}px`,
428
+ overflowY: "auto",
429
+ });
430
+ },
431
+ padding: 8,
432
+ }),
433
+ );
434
+ }
435
+ computePosition(parentLi, submenu, {
436
+ placement: level === 1 ? "bottom-start" : "right-start",
437
+ middleware: middleware,
438
+ }).then(({ x, y, strategy }) => {
439
+ Object.assign(submenu.style, {
440
+ position: strategy,
441
+ left: `${x}px`,
442
+ top: `${y}px`,
443
+ });
424
444
  });
425
- });
426
- });
445
+ },
446
+ getAutoUpdateOptions(),
447
+ );
427
448
  }
428
449
  };
429
450
 
@@ -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.128.3");
159
+ monsterVersion = new Version("4.136.7");
160
160
 
161
161
  return monsterVersion;
162
162
  }
@@ -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
  }
@@ -980,6 +980,52 @@ describe('Select', function () {
980
980
  }, 250);
981
981
  });
982
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
+
983
1029
  it('should clear the unresolved badge state once a local option can resolve the value', function (done) {
984
1030
  this.timeout(4000);
985
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