@schukai/monster 4.145.2 → 4.146.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.
@@ -1,15 +1,14 @@
1
- import {getGlobal} from "../../../../source/types/global.mjs";
2
- import * as chai from 'chai';
3
- import {chaiDom} from "../../../util/chai-dom.mjs";
4
- import {initJSDOM} from "../../../util/jsdom.mjs";
5
- import {ResizeObserverMock} from "../../../util/resize-observer.mjs";
1
+ import { getGlobal } from "../../../../source/types/global.mjs";
2
+ import * as chai from "chai";
3
+ import { chaiDom } from "../../../util/chai-dom.mjs";
4
+ import { initJSDOM } from "../../../util/jsdom.mjs";
5
+ import { ResizeObserverMock } from "../../../util/resize-observer.mjs";
6
6
 
7
7
  let expect = chai.expect;
8
8
  chai.use(chaiDom);
9
9
 
10
10
  const global = getGlobal();
11
11
 
12
-
13
12
  // language=html
14
13
  let html1 = `
15
14
  <monster-tabs id="mytabs">
@@ -145,7 +144,8 @@ let htmlNoDerivedLabel = `
145
144
  </monster-tabs>
146
145
  `;
147
146
 
148
- const svgTabIcon = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M8 1 15 15H1z'/%3E%3C/svg%3E";
147
+ const svgTabIcon =
148
+ "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M8 1 15 15H1z'/%3E%3C/svg%3E";
149
149
  const pngTabIcon = "data:image/png;base64,iVBORw0KGgo=";
150
150
 
151
151
  // language=html
@@ -180,7 +180,7 @@ let VerticalTabs;
180
180
  let restoreBoundingClientRect = null;
181
181
  let restoreResizeObserver = null;
182
182
 
183
- function waitForCondition(check, {timeout = 4000, interval = 10} = {}) {
183
+ function waitForCondition(check, { timeout = 4000, interval = 10 } = {}) {
184
184
  const startedAt = Date.now();
185
185
 
186
186
  return new Promise((resolve, reject) => {
@@ -196,7 +196,7 @@ function waitForCondition(check, {timeout = 4000, interval = 10} = {}) {
196
196
  }
197
197
 
198
198
  if (Date.now() - startedAt > timeout) {
199
- reject(new Error('condition timed out'));
199
+ reject(new Error("condition timed out"));
200
200
  return;
201
201
  }
202
202
 
@@ -207,39 +207,129 @@ function waitForCondition(check, {timeout = 4000, interval = 10} = {}) {
207
207
  });
208
208
  }
209
209
 
210
- describe('Tabs', function () {
210
+ function createRect({ width, height = 40, x = 0, y = 0 }) {
211
+ return {
212
+ width,
213
+ height,
214
+ top: y,
215
+ left: x,
216
+ right: x + width,
217
+ bottom: y + height,
218
+ x,
219
+ y,
220
+ };
221
+ }
222
+
223
+ function installOverflowTabGeometryMock({
224
+ navWidth = 480,
225
+ switchWidth = 44,
226
+ } = {}) {
227
+ const originalGetBoundingClientRect =
228
+ global.HTMLElement.prototype.getBoundingClientRect;
229
+
230
+ global.HTMLElement.prototype.getBoundingClientRect = function () {
231
+ const role = this.getAttribute?.("data-monster-role");
232
+ const label = this.textContent?.trim() || "";
233
+
234
+ if (role === "nav") {
235
+ return createRect({ width: navWidth });
236
+ }
211
237
 
212
- before(function (done) {
213
- initJSDOM().then(() => {
238
+ if (role === "button") {
239
+ return createRect({ width: Math.max(70, label.length * 11) });
240
+ }
214
241
 
215
- import("element-internals-polyfill").catch(e => done(e));
216
-
217
- let promises = []
242
+ if (role === "switch") {
243
+ return createRect({ width: switchWidth });
244
+ }
218
245
 
219
- if (!global['crypto']) {
220
- promises.push(import("@peculiar/webcrypto").then((m) => {
221
- const Crypto = m['Crypto'];
222
- global['crypto'] = new Crypto();
223
- }));
224
- }
246
+ return originalGetBoundingClientRect.call(this);
247
+ };
225
248
 
226
- promises.push(import("../../../../source/components/layout/tabs.mjs").then((m) => {
227
- Tabs = m['Tabs'];
228
- }))
229
- promises.push(import("../../../../source/components/layout/vertical-tabs.mjs").then((m) => {
230
- VerticalTabs = m['VerticalTabs'];
231
- }))
249
+ return () => {
250
+ global.HTMLElement.prototype.getBoundingClientRect =
251
+ originalGetBoundingClientRect;
252
+ };
253
+ }
232
254
 
233
- Promise.all(promises).then(()=>{
234
- done();
235
- }).catch(e => done(e))
255
+ function createDragEvent(type, reference) {
256
+ const event = new Event(type, {
257
+ bubbles: true,
258
+ cancelable: true,
259
+ composed: true,
260
+ });
261
+ event.dataTransfer = {
262
+ effectAllowed: "",
263
+ dropEffect: "",
264
+ setData(format, value) {
265
+ this[format] = value;
266
+ },
267
+ getData(format) {
268
+ return this[format] || "";
269
+ },
270
+ };
271
+ if (reference) {
272
+ event.dataTransfer.setData("text/plain", reference);
273
+ }
274
+ return event;
275
+ }
276
+
277
+ function dragTab(tabs, sourceReference, targetReference) {
278
+ const sourceButton = tabs.shadowRoot.querySelector(
279
+ `button[part=button][data-monster-tab-reference="${sourceReference}"]`,
280
+ );
281
+ const targetButton = tabs.shadowRoot.querySelector(
282
+ `button[part=button][data-monster-tab-reference="${targetReference}"]`,
283
+ );
284
+
285
+ expect(sourceButton).to.not.equal(null);
286
+ expect(targetButton).to.not.equal(null);
287
+
288
+ sourceButton.dispatchEvent(createDragEvent("dragstart", sourceReference));
289
+ targetButton.dispatchEvent(createDragEvent("dragover", sourceReference));
290
+ targetButton.dispatchEvent(createDragEvent("drop", sourceReference));
291
+ sourceButton.dispatchEvent(createDragEvent("dragend", sourceReference));
292
+ }
236
293
 
237
- });
238
- })
294
+ describe("Tabs", function () {
295
+ before(function (done) {
296
+ initJSDOM().then(() => {
297
+ import("element-internals-polyfill").catch((e) => done(e));
239
298
 
240
- describe('document.createElement()', function () {
299
+ let promises = [];
241
300
 
242
- afterEach(() => {
301
+ if (!global["crypto"]) {
302
+ promises.push(
303
+ import("@peculiar/webcrypto").then((m) => {
304
+ const Crypto = m["Crypto"];
305
+ global["crypto"] = new Crypto();
306
+ }),
307
+ );
308
+ }
309
+
310
+ promises.push(
311
+ import("../../../../source/components/layout/tabs.mjs").then((m) => {
312
+ Tabs = m["Tabs"];
313
+ }),
314
+ );
315
+ promises.push(
316
+ import("../../../../source/components/layout/vertical-tabs.mjs").then(
317
+ (m) => {
318
+ VerticalTabs = m["VerticalTabs"];
319
+ },
320
+ ),
321
+ );
322
+
323
+ Promise.all(promises)
324
+ .then(() => {
325
+ done();
326
+ })
327
+ .catch((e) => done(e));
328
+ });
329
+ });
330
+
331
+ describe("document.createElement()", function () {
332
+ afterEach(() => {
243
333
  if (restoreBoundingClientRect instanceof Function) {
244
334
  restoreBoundingClientRect();
245
335
  restoreBoundingClientRect = null;
@@ -248,136 +338,147 @@ describe('Tabs', function () {
248
338
  restoreResizeObserver();
249
339
  restoreResizeObserver = null;
250
340
  }
251
- let mocks = document.getElementById('mocks');
252
- mocks.innerHTML = "";
253
- })
254
-
255
- it('should have buttons and tabs', function (done) {
256
-
257
- let mocks = document.getElementById('mocks');
258
- mocks.innerHTML = html1;
259
-
260
- setTimeout(() => {
261
- try {
262
- const tabs = document.getElementById('mytabs')
263
- expect(tabs).is.instanceof(Tabs);
264
-
265
- setTimeout(() => {
266
- let nav = tabs.shadowRoot.querySelector('nav');
267
- const buttons = tabs.shadowRoot.querySelectorAll('button[part=button]');
268
- expect(buttons[0]).is.instanceof(HTMLButtonElement);
269
- expect(nav.hasChildNodes()).to.be.true;
270
- expect(buttons.length).to.be.equal(4);
271
- done();
272
- }, 100)
273
-
274
- } catch (e) {
275
- return done(e);
276
- }
277
-
278
- }, 0)
341
+ let mocks = document.getElementById("mocks");
342
+ mocks.innerHTML = "";
343
+ });
279
344
 
345
+ it("should have buttons and tabs", function (done) {
346
+ let mocks = document.getElementById("mocks");
347
+ mocks.innerHTML = html1;
280
348
 
281
- });
349
+ setTimeout(() => {
350
+ try {
351
+ const tabs = document.getElementById("mytabs");
352
+ expect(tabs).is.instanceof(Tabs);
282
353
 
283
- it('should shorten label from content when no explicit label is provided', function (done) {
354
+ setTimeout(() => {
355
+ let nav = tabs.shadowRoot.querySelector("nav");
356
+ const buttons = tabs.shadowRoot.querySelectorAll(
357
+ "button[part=button]",
358
+ );
359
+ expect(buttons[0]).is.instanceof(HTMLButtonElement);
360
+ expect(nav.hasChildNodes()).to.be.true;
361
+ expect(buttons.length).to.be.equal(4);
362
+ done();
363
+ }, 100);
364
+ } catch (e) {
365
+ return done(e);
366
+ }
367
+ }, 0);
368
+ });
284
369
 
285
- let mocks = document.getElementById('mocks');
286
- mocks.innerHTML = html1;
370
+ it("should shorten label from content when no explicit label is provided", function (done) {
371
+ let mocks = document.getElementById("mocks");
372
+ mocks.innerHTML = html1;
287
373
 
288
374
  setTimeout(() => {
289
375
  try {
290
- const tabs = document.getElementById('mytabs');
376
+ const tabs = document.getElementById("mytabs");
291
377
  expect(tabs).is.instanceof(Tabs);
292
378
 
293
379
  setTimeout(() => {
294
380
  const thirdTab = tabs.children[2];
295
381
  expect(thirdTab).to.not.equal(undefined);
296
- const reference = thirdTab.getAttribute('id');
382
+ const reference = thirdTab.getAttribute("id");
297
383
  expect(reference).to.not.equal(null);
298
384
  const button = tabs.shadowRoot.querySelector(
299
385
  `button[part=button][data-monster-tab-reference="${reference}"]`,
300
386
  );
301
387
  expect(button).to.not.equal(null);
302
- const labelSpan = button.querySelector('span[data-monster-replace]');
388
+ const labelSpan = button.querySelector(
389
+ "span[data-monster-replace]",
390
+ );
303
391
  expect(labelSpan).to.not.equal(null);
304
- expect(labelSpan.textContent.trim()).to.equal('Das ist tab…');
392
+ expect(labelSpan.textContent.trim()).to.equal("Das ist tab…");
305
393
  done();
306
394
  }, 100);
307
395
  } catch (e) {
308
396
  return done(e);
309
- }
310
- }, 0);
311
- });
312
-
313
- it('should open the first tab by default when no tab is predefined as active', function (done) {
397
+ }
398
+ }, 0);
399
+ });
314
400
 
315
- let mocks = document.getElementById('mocks');
316
- mocks.innerHTML = html1;
401
+ it("should open the first tab by default when no tab is predefined as active", function (done) {
402
+ let mocks = document.getElementById("mocks");
403
+ mocks.innerHTML = html1;
317
404
 
318
405
  setTimeout(() => {
319
406
  try {
320
- const tabs = document.getElementById('mytabs');
407
+ const tabs = document.getElementById("mytabs");
321
408
  expect(tabs).is.instanceof(Tabs);
322
409
 
323
410
  setTimeout(() => {
324
- expect(tabs.children[0].classList.contains('active')).to.equal(true);
325
- expect(tabs.children[1].classList.contains('active')).to.equal(false);
326
- expect(tabs.getActiveTab()).to.equal(tabs.children[0].getAttribute('id'));
411
+ expect(tabs.children[0].classList.contains("active")).to.equal(
412
+ true,
413
+ );
414
+ expect(tabs.children[1].classList.contains("active")).to.equal(
415
+ false,
416
+ );
417
+ expect(tabs.getActiveTab()).to.equal(
418
+ tabs.children[0].getAttribute("id"),
419
+ );
327
420
  done();
328
421
  }, 100);
329
422
  } catch (e) {
330
423
  return done(e);
331
- }
332
- }, 0);
333
- });
334
-
335
- it('should keep a predefined active tab instead of opening the first tab', function (done) {
424
+ }
425
+ }, 0);
426
+ });
336
427
 
337
- let mocks = document.getElementById('mocks');
338
- mocks.innerHTML = htmlActiveSecond;
428
+ it("should keep a predefined active tab instead of opening the first tab", function (done) {
429
+ let mocks = document.getElementById("mocks");
430
+ mocks.innerHTML = htmlActiveSecond;
339
431
 
340
432
  setTimeout(() => {
341
433
  try {
342
- const tabs = document.getElementById('mytabs');
434
+ const tabs = document.getElementById("mytabs");
343
435
  expect(tabs).is.instanceof(Tabs);
344
436
 
345
437
  setTimeout(() => {
346
- expect(tabs.children[0].classList.contains('active')).to.equal(false);
347
- expect(tabs.children[1].classList.contains('active')).to.equal(true);
348
- expect(tabs.getActiveTab()).to.equal(tabs.children[1].getAttribute('id'));
438
+ expect(tabs.children[0].classList.contains("active")).to.equal(
439
+ false,
440
+ );
441
+ expect(tabs.children[1].classList.contains("active")).to.equal(
442
+ true,
443
+ );
444
+ expect(tabs.getActiveTab()).to.equal(
445
+ tabs.children[1].getAttribute("id"),
446
+ );
349
447
  done();
350
448
  }, 100);
351
449
  } catch (e) {
352
450
  return done(e);
353
- }
354
- }, 0);
355
- });
356
-
357
- it('should configure flip and shift middleware for the overflow popper', function (done) {
451
+ }
452
+ }, 0);
453
+ });
358
454
 
359
- let mocks = document.getElementById('mocks');
360
- mocks.innerHTML = html1;
455
+ it("should configure flip and shift middleware for the overflow popper", function (done) {
456
+ let mocks = document.getElementById("mocks");
457
+ mocks.innerHTML = html1;
361
458
 
362
459
  setTimeout(() => {
363
460
  try {
364
- const tabs = document.getElementById('mytabs');
461
+ const tabs = document.getElementById("mytabs");
365
462
  expect(tabs).is.instanceof(Tabs);
366
463
 
367
- const modifiers = tabs.getOption('popper.modifiers');
368
- expect(modifiers.some((entry) => entry?.name === 'flip')).to.equal(true);
369
- expect(modifiers.some((entry) => entry?.name === 'shift')).to.equal(true);
464
+ const modifiers = tabs.getOption("popper.modifiers");
465
+ expect(modifiers.some((entry) => entry?.name === "flip")).to.equal(
466
+ true,
467
+ );
468
+ expect(modifiers.some((entry) => entry?.name === "shift")).to.equal(
469
+ true,
470
+ );
370
471
  done();
371
472
  } catch (e) {
372
473
  return done(e);
373
- }
374
- }, 0);
375
- });
474
+ }
475
+ }, 0);
476
+ });
376
477
 
377
- it('should move overflowing tabs into the popper without losing them', function (done) {
478
+ it("should move overflowing tabs into the popper without losing them", function (done) {
378
479
  this.timeout(5000);
379
480
 
380
- let mocks = document.getElementById('mocks');
481
+ let mocks = document.getElementById("mocks");
381
482
  const OriginalResizeObserver = window.ResizeObserver;
382
483
  const originalGlobalResizeObserver = global.ResizeObserver;
383
484
  class TrackingResizeObserver extends ResizeObserverMock {
@@ -398,54 +499,17 @@ describe('Tabs', function () {
398
499
  window.ResizeObserver = OriginalResizeObserver;
399
500
  global.ResizeObserver = originalGlobalResizeObserver;
400
501
  };
401
- const originalGetBoundingClientRect =
402
- global.HTMLElement.prototype.getBoundingClientRect;
403
- restoreBoundingClientRect = () => {
404
- global.HTMLElement.prototype.getBoundingClientRect =
405
- originalGetBoundingClientRect;
406
- };
407
- global.HTMLElement.prototype.getBoundingClientRect = function () {
408
- const role = this.getAttribute?.('data-monster-role');
409
- const label = this.textContent?.trim() || '';
410
-
411
- if (role === 'nav') {
412
- return {
413
- width: 480,
414
- height: 40,
415
- top: 0,
416
- left: 0,
417
- right: 480,
418
- bottom: 40,
419
- x: 0,
420
- y: 0,
421
- };
422
- }
502
+ restoreBoundingClientRect = installOverflowTabGeometryMock();
423
503
 
424
- if (role === 'button') {
425
- return {
426
- width: Math.max(70, label.length * 11),
427
- height: 40,
428
- top: 0,
429
- left: 0,
430
- right: 0,
431
- bottom: 40,
432
- x: 0,
433
- y: 0,
434
- };
435
- }
436
-
437
- return originalGetBoundingClientRect.call(this);
438
- };
439
-
440
- mocks.innerHTML = htmlOverflow;
504
+ mocks.innerHTML = htmlOverflow;
441
505
 
442
506
  waitForCondition(() => {
443
- const tabs = document.getElementById('overflow-tabs');
507
+ const tabs = document.getElementById("overflow-tabs");
444
508
  const switchButton = tabs?.shadowRoot?.querySelector(
445
509
  '[data-monster-role="switch"]',
446
510
  );
447
- const standardButtons = tabs?.getOption?.('buttons.standard') || [];
448
- const popperButtons = tabs?.getOption?.('buttons.popper') || [];
511
+ const standardButtons = tabs?.getOption?.("buttons.standard") || [];
512
+ const popperButtons = tabs?.getOption?.("buttons.popper") || [];
449
513
 
450
514
  return (
451
515
  tabs instanceof Tabs &&
@@ -453,406 +517,751 @@ describe('Tabs', function () {
453
517
  standardButtons.length > 0 &&
454
518
  popperButtons.length > 0 &&
455
519
  standardButtons.length + popperButtons.length === 11 &&
456
- switchButton.classList.contains('hidden') === false
520
+ switchButton.classList.contains("hidden") === false
457
521
  );
458
- }).then(() => {
459
- try {
460
- const tabs = document.getElementById('overflow-tabs');
461
- expect(tabs).is.instanceof(Tabs);
462
- const switchButton = tabs.shadowRoot.querySelector(
463
- '[data-monster-role="switch"]',
464
- );
465
- let standardButtons = tabs.getOption('buttons.standard');
466
- let popperButtons = tabs.getOption('buttons.popper');
522
+ })
523
+ .then(() => {
524
+ let tabs;
525
+ let originalSetOption;
526
+ try {
527
+ tabs = document.getElementById("overflow-tabs");
528
+ expect(tabs).is.instanceof(Tabs);
529
+ const switchButton = tabs.shadowRoot.querySelector(
530
+ '[data-monster-role="switch"]',
531
+ );
532
+ let standardButtons = tabs.getOption("buttons.standard");
533
+ let popperButtons = tabs.getOption("buttons.popper");
534
+
535
+ expect(switchButton).to.not.equal(null);
536
+ expect(standardButtons.length).to.be.greaterThan(0);
537
+ expect(popperButtons.length).to.be.greaterThan(0);
538
+ expect(standardButtons.length + popperButtons.length).to.equal(11);
539
+ expect(switchButton.classList.contains("hidden")).to.equal(false);
540
+
541
+ const resizeObserver = TrackingResizeObserver.instances.find(
542
+ (observer) =>
543
+ observer.elements.includes(
544
+ tabs.shadowRoot.querySelector('[data-monster-role="nav"]'),
545
+ ),
546
+ );
547
+ expect(resizeObserver).to.not.equal(undefined);
548
+
549
+ originalSetOption = tabs.setOption.bind(tabs);
550
+ let resetAllTabsDuringResize = false;
551
+ tabs.setOption = function (path, value) {
552
+ if (
553
+ path === "buttons" &&
554
+ value?.standard?.length === 11 &&
555
+ value?.popper?.length === 0
556
+ ) {
557
+ resetAllTabsDuringResize = true;
558
+ }
559
+ return originalSetOption(path, value);
560
+ };
561
+
562
+ resizeObserver.triggerResize([]);
467
563
 
468
- expect(switchButton).to.not.equal(null);
469
- expect(standardButtons.length).to.be.greaterThan(0);
470
- expect(popperButtons.length).to.be.greaterThan(0);
471
- expect(standardButtons.length + popperButtons.length).to.equal(11);
472
- expect(switchButton.classList.contains('hidden')).to.equal(false);
564
+ return waitForCondition(() => {
565
+ return TrackingResizeObserver.callbackCount > 0;
566
+ })
567
+ .then(() => {
568
+ return new Promise((resolve) => setTimeout(resolve, 260));
569
+ })
570
+ .then(() => {
571
+ return new Promise((resolve) => requestAnimationFrame(resolve));
572
+ })
573
+ .then(() => {
574
+ tabs.setOption = originalSetOption;
575
+ expect(resetAllTabsDuringResize).to.equal(false);
576
+ standardButtons = tabs.getOption("buttons.standard");
577
+ popperButtons = tabs.getOption("buttons.popper");
578
+ expect(standardButtons.length).to.be.greaterThan(0);
579
+ expect(popperButtons.length).to.be.greaterThan(0);
580
+ expect(standardButtons.length + popperButtons.length).to.equal(
581
+ 11,
582
+ );
583
+ expect(
584
+ new Set(
585
+ standardButtons
586
+ .concat(popperButtons)
587
+ .map((button) => button.reference),
588
+ ).size,
589
+ ).to.equal(11);
590
+ })
591
+ .then(() => {
592
+ restoreBoundingClientRect();
593
+ restoreBoundingClientRect = null;
594
+ restoreResizeObserver();
595
+ restoreResizeObserver = null;
596
+ done();
597
+ });
598
+ } catch (e) {
599
+ if (tabs && originalSetOption) {
600
+ tabs.setOption = originalSetOption;
601
+ }
602
+ restoreBoundingClientRect();
603
+ restoreBoundingClientRect = null;
604
+ restoreResizeObserver();
605
+ restoreResizeObserver = null;
606
+ return done(e);
607
+ }
608
+ })
609
+ .catch((e) => {
610
+ const tabs = document.getElementById("overflow-tabs");
611
+ if (tabs && Object.prototype.hasOwnProperty.call(tabs, "setOption")) {
612
+ delete tabs.setOption;
613
+ }
614
+ if (restoreBoundingClientRect instanceof Function) {
615
+ restoreBoundingClientRect();
616
+ restoreBoundingClientRect = null;
617
+ }
618
+ if (restoreResizeObserver instanceof Function) {
619
+ restoreResizeObserver();
620
+ restoreResizeObserver = null;
621
+ }
622
+ done(e);
623
+ });
624
+ });
473
625
 
474
- const resizeObserver = TrackingResizeObserver.instances.find(
475
- (observer) => observer.elements.includes(tabs.shadowRoot.querySelector('[data-monster-role="nav"]')),
626
+ it("should preserve the overflow split and recalculate widths while rebuilding tab buttons", async function () {
627
+ this.timeout(5000);
628
+
629
+ const mocks = document.getElementById("mocks");
630
+ restoreBoundingClientRect = installOverflowTabGeometryMock();
631
+
632
+ let tabs;
633
+ let originalSetOption;
634
+
635
+ try {
636
+ mocks.innerHTML = htmlOverflow;
637
+
638
+ await waitForCondition(() => {
639
+ tabs = document.getElementById("overflow-tabs");
640
+ const standardButtons = tabs?.getOption?.("buttons.standard") || [];
641
+ const popperButtons = tabs?.getOption?.("buttons.popper") || [];
642
+
643
+ return (
644
+ tabs instanceof Tabs &&
645
+ standardButtons.length > 0 &&
646
+ popperButtons.length > 0 &&
647
+ standardButtons.length + popperButtons.length === 11
476
648
  );
477
- expect(resizeObserver).to.not.equal(undefined);
478
- resizeObserver.triggerResize([]);
649
+ });
479
650
 
480
- return waitForCondition(() => {
481
- return (
482
- TrackingResizeObserver.callbackCount > 0 &&
483
- tabs.getOption('buttons.standard').length === 11 &&
484
- tabs.getOption('buttons.popper').length === 0
485
- );
486
- }).then(() => {
487
- return waitForCondition(() => {
488
- standardButtons = tabs.getOption('buttons.standard');
489
- popperButtons = tabs.getOption('buttons.popper');
651
+ const initialPopperCount = tabs.getOption("buttons.popper").length;
652
+ const standardReference = tabs.getOption(
653
+ "buttons.standard.0.reference",
654
+ );
655
+ const panel = document.getElementById(standardReference);
656
+ expect(panel).to.not.equal(null);
657
+
658
+ originalSetOption = tabs.setOption.bind(tabs);
659
+ let resetAllTabsDuringRebuild = false;
660
+ tabs.setOption = function (path, value) {
661
+ if (
662
+ path === "buttons" &&
663
+ value?.standard?.length === 11 &&
664
+ value?.popper?.length === 0
665
+ ) {
666
+ resetAllTabsDuringRebuild = true;
667
+ }
668
+ return originalSetOption(path, value);
669
+ };
670
+
671
+ panel.setAttribute(
672
+ "data-monster-button-label",
673
+ "Aktualisierte sehr lange Tab Beschriftung mit viel mehr Platzbedarf",
674
+ );
675
+
676
+ await waitForCondition(() => {
677
+ return tabs
678
+ .getOption("buttons.standard", [])
679
+ .concat(tabs.getOption("buttons.popper", []))
680
+ .some((button) => {
490
681
  return (
491
- standardButtons.length > 0 &&
492
- popperButtons.length > 0 &&
493
- standardButtons.length + popperButtons.length === 11 &&
494
- new Set(
495
- standardButtons
496
- .concat(popperButtons)
497
- .map((button) => button.reference),
498
- ).size === 11
682
+ button.reference === standardReference &&
683
+ button.label.includes("Aktualisierte")
499
684
  );
500
685
  });
501
- }).then(() => {
502
- restoreBoundingClientRect();
503
- restoreBoundingClientRect = null;
504
- restoreResizeObserver();
505
- restoreResizeObserver = null;
506
- done();
507
- });
508
- } catch (e) {
509
- restoreBoundingClientRect();
510
- restoreBoundingClientRect = null;
511
- restoreResizeObserver();
512
- restoreResizeObserver = null;
513
- return done(e);
514
- }
515
- }).catch((e) => {
516
- if (restoreBoundingClientRect instanceof Function) {
517
- restoreBoundingClientRect();
518
- restoreBoundingClientRect = null;
519
- }
520
- if (restoreResizeObserver instanceof Function) {
521
- restoreResizeObserver();
522
- restoreResizeObserver = null;
686
+ });
687
+ await waitForCondition(() => {
688
+ return tabs.getOption("buttons.popper").length > initialPopperCount;
689
+ });
690
+
691
+ tabs.setOption = originalSetOption;
692
+ expect(resetAllTabsDuringRebuild).to.equal(false);
693
+ expect(tabs.getOption("buttons.popper").length).to.be.greaterThan(0);
694
+ expect(
695
+ tabs
696
+ .getOption("buttons.standard")
697
+ .concat(tabs.getOption("buttons.popper")).length,
698
+ ).to.equal(11);
699
+ } finally {
700
+ if (tabs && originalSetOption) {
701
+ tabs.setOption = originalSetOption;
523
702
  }
524
- done(e);
525
- });
526
- });
703
+ restoreBoundingClientRect();
704
+ restoreBoundingClientRect = null;
705
+ }
706
+ });
527
707
 
528
- it('should ignore unavailable panels when creating tabs and buttons', function (done) {
529
- let mocks = document.getElementById('mocks');
708
+ it("should ignore unavailable panels when creating tabs and buttons", function (done) {
709
+ let mocks = document.getElementById("mocks");
530
710
  mocks.innerHTML = htmlAvailability;
531
711
 
532
712
  waitForCondition(() => {
533
- const tabs = document.getElementById('availability-tabs');
713
+ const tabs = document.getElementById("availability-tabs");
534
714
  return (
535
715
  tabs instanceof Tabs &&
536
- tabs.shadowRoot.querySelectorAll('button[part=button]').length === 2 &&
537
- tabs.getActiveTab() === 'overview'
716
+ tabs.shadowRoot.querySelectorAll("button[part=button]").length ===
717
+ 2 &&
718
+ tabs.getActiveTab() === "overview"
538
719
  );
539
- }).then(() => {
540
- try {
541
- const tabs = document.getElementById('availability-tabs');
542
- const vacation = document.getElementById('vacation-panel');
543
- const buttons = tabs.shadowRoot.querySelectorAll('button[part=button]');
544
-
545
- expect(tabs.getTabs().length).to.equal(2);
546
- expect(tabs.getTabs().includes(vacation)).to.equal(false);
547
- expect(tabs.shadowRoot.querySelector(
548
- `button[part=button][data-monster-tab-reference="${vacation.getAttribute('id')}"]`,
549
- )).to.equal(null);
550
- expect(buttons[0].textContent.trim()).to.equal('Overview');
551
- expect(buttons[1].textContent.trim()).to.equal('Details');
552
- expect(tabs.getActiveTab()).to.equal('overview');
553
- done();
554
- } catch (e) {
555
- done(e);
556
- }
557
- }).catch(done);
720
+ })
721
+ .then(() => {
722
+ try {
723
+ const tabs = document.getElementById("availability-tabs");
724
+ const vacation = document.getElementById("vacation-panel");
725
+ const buttons = tabs.shadowRoot.querySelectorAll(
726
+ "button[part=button]",
727
+ );
728
+
729
+ expect(tabs.getTabs().length).to.equal(2);
730
+ expect(tabs.getTabs().includes(vacation)).to.equal(false);
731
+ expect(
732
+ tabs.shadowRoot.querySelector(
733
+ `button[part=button][data-monster-tab-reference="${vacation.getAttribute("id")}"]`,
734
+ ),
735
+ ).to.equal(null);
736
+ expect(buttons[0].textContent.trim()).to.equal("Overview");
737
+ expect(buttons[1].textContent.trim()).to.equal("Details");
738
+ expect(tabs.getActiveTab()).to.equal("overview");
739
+ done();
740
+ } catch (e) {
741
+ done(e);
742
+ }
743
+ })
744
+ .catch(done);
558
745
  });
559
746
 
560
- it('should ignore unavailable tabs in activeTab and rebuild when they become available', function (done) {
561
- let mocks = document.getElementById('mocks');
747
+ it("should ignore unavailable tabs in activeTab and rebuild when they become available", function (done) {
748
+ let mocks = document.getElementById("mocks");
562
749
  mocks.innerHTML = htmlAvailability;
563
750
 
564
751
  waitForCondition(() => {
565
- const tabs = document.getElementById('availability-tabs');
566
- return tabs instanceof Tabs && tabs.getActiveTab() === 'overview';
567
- }).then(() => {
568
- const tabs = document.getElementById('availability-tabs');
569
- const vacation = document.getElementById('vacation-panel');
570
- let availabilityEvent = null;
571
- tabs.addEventListener('monster-tab-availability-changed', (event) => {
572
- availabilityEvent = event;
573
- });
752
+ const tabs = document.getElementById("availability-tabs");
753
+ return tabs instanceof Tabs && tabs.getActiveTab() === "overview";
754
+ })
755
+ .then(() => {
756
+ const tabs = document.getElementById("availability-tabs");
757
+ const vacation = document.getElementById("vacation-panel");
758
+ let availabilityEvent = null;
759
+ tabs.addEventListener("monster-tab-availability-changed", (event) => {
760
+ availabilityEvent = event;
761
+ });
574
762
 
575
- tabs.activeTab('vacation');
576
- expect(tabs.getActiveTab()).to.equal('overview');
763
+ tabs.activeTab("vacation");
764
+ expect(tabs.getActiveTab()).to.equal("overview");
577
765
 
578
- vacation.setAttribute('data-monster-tab-available', 'true');
766
+ vacation.setAttribute("data-monster-tab-available", "true");
579
767
 
580
- return waitForCondition(() => {
581
- return tabs.shadowRoot.querySelectorAll('button[part=button]').length === 3;
582
- }).then(() => {
583
- try {
584
- expect(availabilityEvent).to.not.equal(null);
585
- expect(availabilityEvent.detail.name).to.equal('vacation');
586
- expect(availabilityEvent.detail.available).to.equal(true);
768
+ return waitForCondition(() => {
769
+ return (
770
+ tabs.shadowRoot.querySelectorAll("button[part=button]").length ===
771
+ 3
772
+ );
773
+ }).then(() => {
774
+ try {
775
+ expect(availabilityEvent).to.not.equal(null);
776
+ expect(availabilityEvent.detail.name).to.equal("vacation");
777
+ expect(availabilityEvent.detail.available).to.equal(true);
587
778
 
588
- tabs.activeTab('vacation');
589
- expect(tabs.getActiveTab()).to.equal('vacation');
590
- done();
591
- } catch (e) {
592
- done(e);
593
- }
594
- }, 100);
595
- }).catch(done);
779
+ tabs.activeTab("vacation");
780
+ expect(tabs.getActiveTab()).to.equal("vacation");
781
+ done();
782
+ } catch (e) {
783
+ done(e);
784
+ }
785
+ }, 100);
786
+ })
787
+ .catch(done);
596
788
  });
597
789
 
598
- it('should fallback when the active tab becomes unavailable', function (done) {
599
- let mocks = document.getElementById('mocks');
790
+ it("should fallback when the active tab becomes unavailable", function (done) {
791
+ let mocks = document.getElementById("mocks");
600
792
  mocks.innerHTML = htmlActiveUnavailable;
601
793
 
602
794
  waitForCondition(() => {
603
- const tabs = document.getElementById('fallback-tabs');
604
- return tabs instanceof Tabs && tabs.getActiveTab() === 'second';
605
- }).then(() => {
606
- const tabs = document.getElementById('fallback-tabs');
607
- const activePanel = document.getElementById('second-panel');
608
- activePanel.setAttribute('data-monster-tab-available', 'false');
609
-
610
- return waitForCondition(() => tabs.getActiveTab() === 'first').then(() => {
611
- try {
612
- expect(activePanel.classList.contains('active')).to.equal(false);
613
- expect(tabs.shadowRoot.querySelectorAll('button[part=button]').length).to.equal(2);
614
- done();
615
- } catch (e) {
616
- done(e);
617
- }
618
- });
619
- }).catch(done);
795
+ const tabs = document.getElementById("fallback-tabs");
796
+ return tabs instanceof Tabs && tabs.getActiveTab() === "second";
797
+ })
798
+ .then(() => {
799
+ const tabs = document.getElementById("fallback-tabs");
800
+ const activePanel = document.getElementById("second-panel");
801
+ activePanel.setAttribute("data-monster-tab-available", "false");
802
+
803
+ return waitForCondition(() => tabs.getActiveTab() === "first").then(
804
+ () => {
805
+ try {
806
+ expect(activePanel.classList.contains("active")).to.equal(
807
+ false,
808
+ );
809
+ expect(
810
+ tabs.shadowRoot.querySelectorAll("button[part=button]")
811
+ .length,
812
+ ).to.equal(2);
813
+ done();
814
+ } catch (e) {
815
+ done(e);
816
+ }
817
+ },
818
+ );
819
+ })
820
+ .catch(done);
620
821
  });
621
822
 
622
- it('should fallback when the active tab is removed directly', function (done) {
623
- let mocks = document.getElementById('mocks');
823
+ it("should fallback when the active tab is removed directly", function (done) {
824
+ let mocks = document.getElementById("mocks");
624
825
  mocks.innerHTML = htmlRemoveFallback;
625
826
 
626
827
  waitForCondition(() => {
627
- const tabs = document.getElementById('remove-fallback-tabs');
828
+ const tabs = document.getElementById("remove-fallback-tabs");
628
829
  return (
629
830
  tabs instanceof Tabs &&
630
- tabs.getActiveTab() === 'second' &&
631
- tabs.shadowRoot.querySelectorAll('button[part=button]').length === 3
831
+ tabs.getActiveTab() === "second" &&
832
+ tabs.shadowRoot.querySelectorAll("button[part=button]").length === 3
632
833
  );
633
- }).then(() => {
634
- const tabs = document.getElementById('remove-fallback-tabs');
635
- setTimeout(() => {
636
- document.getElementById('remove-second-panel').remove();
637
-
638
- waitForCondition(() => tabs.getActiveTab() === 'first').then(() => {
639
- try {
640
- expect(tabs.shadowRoot.querySelectorAll('button[part=button]').length).to.equal(2);
641
- done();
642
- } catch (e) {
643
- done(e);
644
- }
645
- }).catch(done);
646
- });
647
- }).catch(done);
834
+ })
835
+ .then(() => {
836
+ const tabs = document.getElementById("remove-fallback-tabs");
837
+ setTimeout(() => {
838
+ document.getElementById("remove-second-panel").remove();
839
+
840
+ waitForCondition(() => tabs.getActiveTab() === "first")
841
+ .then(() => {
842
+ try {
843
+ expect(
844
+ tabs.shadowRoot.querySelectorAll("button[part=button]")
845
+ .length,
846
+ ).to.equal(2);
847
+ done();
848
+ } catch (e) {
849
+ done(e);
850
+ }
851
+ })
852
+ .catch(done);
853
+ });
854
+ })
855
+ .catch(done);
648
856
  });
649
857
 
650
- it('should keep disabled tabs visible but not activatable', function (done) {
651
- let mocks = document.getElementById('mocks');
858
+ it("should keep disabled tabs visible but not activatable", function (done) {
859
+ let mocks = document.getElementById("mocks");
652
860
  mocks.innerHTML = htmlDisabled;
653
861
 
654
862
  waitForCondition(() => {
655
- const tabs = document.getElementById('disabled-tabs');
656
- const disabledPanel = document.getElementById('disabled-panel');
863
+ const tabs = document.getElementById("disabled-tabs");
864
+ const disabledPanel = document.getElementById("disabled-panel");
657
865
  const disabledButton = tabs?.shadowRoot?.querySelector(
658
- `button[part=button][data-monster-tab-reference="${disabledPanel?.getAttribute('id')}"]`,
866
+ `button[part=button][data-monster-tab-reference="${disabledPanel?.getAttribute("id")}"]`,
659
867
  );
660
868
  return (
661
869
  tabs instanceof Tabs &&
662
870
  disabledButton instanceof HTMLButtonElement &&
663
- tabs.getActiveTab() === 'enabled'
871
+ tabs.getActiveTab() === "enabled"
664
872
  );
665
- }).then(() => {
666
- try {
667
- const tabs = document.getElementById('disabled-tabs');
668
- const disabledPanel = document.getElementById('disabled-panel');
669
- const disabledButton = tabs.shadowRoot.querySelector(
670
- `button[part=button][data-monster-tab-reference="${disabledPanel.getAttribute('id')}"]`,
671
- );
873
+ })
874
+ .then(() => {
875
+ try {
876
+ const tabs = document.getElementById("disabled-tabs");
877
+ const disabledPanel = document.getElementById("disabled-panel");
878
+ const disabledButton = tabs.shadowRoot.querySelector(
879
+ `button[part=button][data-monster-tab-reference="${disabledPanel.getAttribute("id")}"]`,
880
+ );
672
881
 
673
- expect(disabledButton.disabled).to.equal(true);
674
- expect(disabledButton.getAttribute('title')).to.equal('Only available for employees');
675
- tabs.activeTab('disabled');
676
- expect(tabs.getActiveTab()).to.equal('enabled');
677
- done();
678
- } catch (e) {
679
- done(e);
680
- }
681
- }).catch(done);
882
+ expect(disabledButton.disabled).to.equal(true);
883
+ expect(disabledButton.getAttribute("title")).to.equal(
884
+ "Only available for employees",
885
+ );
886
+ tabs.activeTab("disabled");
887
+ expect(tabs.getActiveTab()).to.equal("enabled");
888
+ done();
889
+ } catch (e) {
890
+ done(e);
891
+ }
892
+ })
893
+ .catch(done);
682
894
  });
683
895
 
684
- it('should expose refresh, sync, hide and show APIs', function (done) {
685
- let mocks = document.getElementById('mocks');
896
+ it("should expose refresh, sync, hide and show APIs", function (done) {
897
+ let mocks = document.getElementById("mocks");
686
898
  mocks.innerHTML = htmlAvailability;
687
899
 
688
900
  waitForCondition(() => {
689
- const tabs = document.getElementById('availability-tabs');
690
- return tabs instanceof Tabs && tabs.shadowRoot.querySelectorAll('button[part=button]').length === 2;
691
- }).then(() => {
692
- const tabs = document.getElementById('availability-tabs');
693
-
694
- tabs.showTab('vacation');
695
- return tabs.refreshTabs().then((result) => {
696
- expect(result).to.equal(tabs);
697
- expect(tabs.getTabs().length).to.equal(3);
698
-
699
- tabs.hideTab('vacation');
700
- return tabs.syncTabs();
701
- }).then((result) => {
702
- try {
703
- expect(result).to.equal(tabs);
704
- expect(tabs.getTabs().length).to.equal(2);
705
- expect(document.getElementById('vacation-panel').getAttribute('data-monster-tab-available')).to.equal('false');
706
- done();
707
- } catch (e) {
708
- done(e);
709
- }
710
- });
711
- }).catch(done);
901
+ const tabs = document.getElementById("availability-tabs");
902
+ return (
903
+ tabs instanceof Tabs &&
904
+ tabs.shadowRoot.querySelectorAll("button[part=button]").length === 2
905
+ );
906
+ })
907
+ .then(() => {
908
+ const tabs = document.getElementById("availability-tabs");
909
+
910
+ tabs.showTab("vacation");
911
+ return tabs
912
+ .refreshTabs()
913
+ .then((result) => {
914
+ expect(result).to.equal(tabs);
915
+ expect(tabs.getTabs().length).to.equal(3);
916
+
917
+ tabs.hideTab("vacation");
918
+ return tabs.syncTabs();
919
+ })
920
+ .then((result) => {
921
+ try {
922
+ expect(result).to.equal(tabs);
923
+ expect(tabs.getTabs().length).to.equal(2);
924
+ expect(
925
+ document
926
+ .getElementById("vacation-panel")
927
+ .getAttribute("data-monster-tab-available"),
928
+ ).to.equal("false");
929
+ done();
930
+ } catch (e) {
931
+ done(e);
932
+ }
933
+ });
934
+ })
935
+ .catch(done);
712
936
  });
713
937
 
714
- it('should not derive labels from content when disabled by option', function (done) {
715
- let mocks = document.getElementById('mocks');
938
+ it("should not derive labels from content when disabled by option", function (done) {
939
+ let mocks = document.getElementById("mocks");
716
940
  mocks.innerHTML = htmlNoDerivedLabel;
717
941
 
718
942
  waitForCondition(() => {
719
- const tabs = document.getElementById('label-tabs');
720
- return tabs instanceof Tabs && tabs.shadowRoot.querySelector('button[part=button]') !== null;
721
- }).then(() => {
722
- try {
723
- const tabs = document.getElementById('label-tabs');
724
- const button = tabs.shadowRoot.querySelector('button[part=button]');
725
- expect(button.textContent.trim()).to.equal('New Tab');
726
- expect(document.getElementById('content-panel').hasAttribute('data-monster-button-label')).to.equal(false);
727
- done();
728
- } catch (e) {
729
- done(e);
730
- }
731
- }).catch(done);
943
+ const tabs = document.getElementById("label-tabs");
944
+ return (
945
+ tabs instanceof Tabs &&
946
+ tabs.shadowRoot.querySelector("button[part=button]") !== null
947
+ );
948
+ })
949
+ .then(() => {
950
+ try {
951
+ const tabs = document.getElementById("label-tabs");
952
+ const button = tabs.shadowRoot.querySelector("button[part=button]");
953
+ expect(button.textContent.trim()).to.equal("New Tab");
954
+ expect(
955
+ document
956
+ .getElementById("content-panel")
957
+ .hasAttribute("data-monster-button-label"),
958
+ ).to.equal(false);
959
+ done();
960
+ } catch (e) {
961
+ done(e);
962
+ }
963
+ })
964
+ .catch(done);
732
965
  });
733
966
 
734
- it('should render SVG tab icons as theme-aware masks by default', function (done) {
735
- let mocks = document.getElementById('mocks');
967
+ it("should render SVG tab icons as theme-aware masks by default", function (done) {
968
+ let mocks = document.getElementById("mocks");
736
969
  mocks.innerHTML = htmlIconModes;
737
970
 
738
971
  waitForCondition(() => {
739
- const tabs = document.getElementById('icon-mode-tabs');
740
- return tabs instanceof Tabs && tabs.shadowRoot.querySelectorAll('button[part=button]').length === 4;
741
- }).then(() => {
742
- try {
743
- const tabs = document.getElementById('icon-mode-tabs');
744
- const getIcon = (reference) => tabs.shadowRoot.querySelector(
745
- `button[part=button][data-monster-tab-reference="${reference}"] [part=icon]`,
746
- );
747
-
748
- const autoSvgIcon = getIcon('auto-svg-panel');
749
- expect(autoSvgIcon.tagName).to.equal('SPAN');
750
- expect(autoSvgIcon.getAttribute('data-monster-icon-mode')).to.equal('mask');
751
- expect(autoSvgIcon.getAttribute('aria-hidden')).to.equal('true');
752
- expect(autoSvgIcon.getAttribute('style')).to.contain('--monster-tab-icon-mask-image');
753
-
754
- const imageSvgIcon = getIcon('image-svg-panel');
755
- expect(imageSvgIcon.tagName).to.equal('IMG');
756
- expect(imageSvgIcon.getAttribute('data-monster-icon-mode')).to.equal('image');
757
- expect(imageSvgIcon.getAttribute('src')).to.equal(svgTabIcon);
972
+ const tabs = document.getElementById("icon-mode-tabs");
973
+ return (
974
+ tabs instanceof Tabs &&
975
+ tabs.shadowRoot.querySelectorAll("button[part=button]").length === 4
976
+ );
977
+ })
978
+ .then(() => {
979
+ try {
980
+ const tabs = document.getElementById("icon-mode-tabs");
981
+ const getIcon = (reference) =>
982
+ tabs.shadowRoot.querySelector(
983
+ `button[part=button][data-monster-tab-reference="${reference}"] [part=icon]`,
984
+ );
758
985
 
759
- const autoRasterIcon = getIcon('auto-raster-panel');
760
- expect(autoRasterIcon.tagName).to.equal('IMG');
761
- expect(autoRasterIcon.getAttribute('data-monster-icon-mode')).to.equal('image');
762
- expect(autoRasterIcon.getAttribute('src')).to.equal(pngTabIcon);
986
+ const autoSvgIcon = getIcon("auto-svg-panel");
987
+ expect(autoSvgIcon.tagName).to.equal("SPAN");
988
+ expect(autoSvgIcon.getAttribute("data-monster-icon-mode")).to.equal(
989
+ "mask",
990
+ );
991
+ expect(autoSvgIcon.getAttribute("aria-hidden")).to.equal("true");
992
+ expect(autoSvgIcon.getAttribute("style")).to.contain(
993
+ "--monster-tab-icon-mask-image",
994
+ );
763
995
 
764
- const maskRasterIcon = getIcon('mask-raster-panel');
765
- expect(maskRasterIcon.tagName).to.equal('SPAN');
766
- expect(maskRasterIcon.getAttribute('data-monster-icon-mode')).to.equal('mask');
767
- expect(maskRasterIcon.getAttribute('style')).to.contain('--monster-tab-icon-mask-image');
996
+ const imageSvgIcon = getIcon("image-svg-panel");
997
+ expect(imageSvgIcon.tagName).to.equal("IMG");
998
+ expect(
999
+ imageSvgIcon.getAttribute("data-monster-icon-mode"),
1000
+ ).to.equal("image");
1001
+ expect(imageSvgIcon.getAttribute("src")).to.equal(svgTabIcon);
1002
+
1003
+ const autoRasterIcon = getIcon("auto-raster-panel");
1004
+ expect(autoRasterIcon.tagName).to.equal("IMG");
1005
+ expect(
1006
+ autoRasterIcon.getAttribute("data-monster-icon-mode"),
1007
+ ).to.equal("image");
1008
+ expect(autoRasterIcon.getAttribute("src")).to.equal(pngTabIcon);
1009
+
1010
+ const maskRasterIcon = getIcon("mask-raster-panel");
1011
+ expect(maskRasterIcon.tagName).to.equal("SPAN");
1012
+ expect(
1013
+ maskRasterIcon.getAttribute("data-monster-icon-mode"),
1014
+ ).to.equal("mask");
1015
+ expect(maskRasterIcon.getAttribute("style")).to.contain(
1016
+ "--monster-tab-icon-mask-image",
1017
+ );
768
1018
 
769
- done();
770
- } catch (e) {
771
- done(e);
772
- }
773
- }).catch(done);
1019
+ done();
1020
+ } catch (e) {
1021
+ done(e);
1022
+ }
1023
+ })
1024
+ .catch(done);
774
1025
  });
775
1026
 
776
- it('should update tab icon mode when the icon mode attribute changes', function (done) {
777
- let mocks = document.getElementById('mocks');
1027
+ it("should update tab icon mode when the icon mode attribute changes", function (done) {
1028
+ let mocks = document.getElementById("mocks");
778
1029
  mocks.innerHTML = htmlIconModes;
779
1030
 
780
1031
  waitForCondition(() => {
781
- const tabs = document.getElementById('icon-mode-tabs');
782
- return tabs instanceof Tabs && tabs.shadowRoot.querySelectorAll('button[part=button]').length === 4;
783
- }).then(() => {
784
- const tabs = document.getElementById('icon-mode-tabs');
785
- const panel = document.getElementById('image-svg-panel');
786
- panel.setAttribute('data-monster-button-icon-mode', 'mask');
787
-
788
- return waitForCondition(() => {
789
- const icon = tabs.shadowRoot.querySelector(
790
- 'button[part=button][data-monster-tab-reference="image-svg-panel"] [part=icon]',
791
- );
792
- return icon?.tagName === 'SPAN' && icon.getAttribute('data-monster-icon-mode') === 'mask';
793
- });
794
- }).then(() => {
795
- done();
796
- }).catch(done);
1032
+ const tabs = document.getElementById("icon-mode-tabs");
1033
+ return (
1034
+ tabs instanceof Tabs &&
1035
+ tabs.shadowRoot.querySelectorAll("button[part=button]").length === 4
1036
+ );
1037
+ })
1038
+ .then(() => {
1039
+ const tabs = document.getElementById("icon-mode-tabs");
1040
+ const panel = document.getElementById("image-svg-panel");
1041
+ panel.setAttribute("data-monster-button-icon-mode", "mask");
1042
+
1043
+ return waitForCondition(() => {
1044
+ const icon = tabs.shadowRoot.querySelector(
1045
+ 'button[part=button][data-monster-tab-reference="image-svg-panel"] [part=icon]',
1046
+ );
1047
+ return (
1048
+ icon?.tagName === "SPAN" &&
1049
+ icon.getAttribute("data-monster-icon-mode") === "mask"
1050
+ );
1051
+ });
1052
+ })
1053
+ .then(() => {
1054
+ done();
1055
+ })
1056
+ .catch(done);
797
1057
  });
798
1058
 
799
- it('should render vertical tab SVG icons as theme-aware masks by default', function (done) {
800
- let mocks = document.getElementById('mocks');
1059
+ it("should render vertical tab SVG icons as theme-aware masks by default", function (done) {
1060
+ let mocks = document.getElementById("mocks");
801
1061
  mocks.innerHTML = htmlVerticalIconModes;
802
1062
 
803
1063
  waitForCondition(() => {
804
- const tabs = document.getElementById('vertical-icon-mode-tabs');
805
- return tabs instanceof VerticalTabs && tabs.shadowRoot.querySelector('button[part=button] [part=icon]') !== null;
806
- }).then(() => {
807
- try {
808
- const tabs = document.getElementById('vertical-icon-mode-tabs');
809
- const icon = tabs.shadowRoot.querySelector(
810
- 'button[part=button][data-monster-tab-reference="vertical-auto-svg-panel"] [part=icon]',
811
- );
812
- expect(icon.tagName).to.equal('SPAN');
813
- expect(icon.getAttribute('data-monster-icon-mode')).to.equal('mask');
814
- expect(icon.getAttribute('style')).to.contain('--monster-tab-icon-mask-image');
815
- done();
816
- } catch (e) {
817
- done(e);
818
- }
819
- }).catch(done);
1064
+ const tabs = document.getElementById("vertical-icon-mode-tabs");
1065
+ return (
1066
+ tabs instanceof VerticalTabs &&
1067
+ tabs.shadowRoot.querySelector("button[part=button] [part=icon]") !==
1068
+ null
1069
+ );
1070
+ })
1071
+ .then(() => {
1072
+ try {
1073
+ const tabs = document.getElementById("vertical-icon-mode-tabs");
1074
+ const icon = tabs.shadowRoot.querySelector(
1075
+ 'button[part=button][data-monster-tab-reference="vertical-auto-svg-panel"] [part=icon]',
1076
+ );
1077
+ expect(icon.tagName).to.equal("SPAN");
1078
+ expect(icon.getAttribute("data-monster-icon-mode")).to.equal(
1079
+ "mask",
1080
+ );
1081
+ expect(icon.getAttribute("style")).to.contain(
1082
+ "--monster-tab-icon-mask-image",
1083
+ );
1084
+ done();
1085
+ } catch (e) {
1086
+ done(e);
1087
+ }
1088
+ })
1089
+ .catch(done);
820
1090
  });
821
1091
 
822
- it('should include stable names and metadata in tab events and buttons', function (done) {
823
- let mocks = document.getElementById('mocks');
1092
+ it("should include stable names and metadata in tab events and buttons", function (done) {
1093
+ let mocks = document.getElementById("mocks");
824
1094
  mocks.innerHTML = htmlMetadata;
825
1095
 
826
1096
  waitForCondition(() => {
827
- const tabs = document.getElementById('metadata-tabs');
828
- return tabs instanceof Tabs && tabs.shadowRoot.querySelector('button[part=button]') !== null;
829
- }).then(() => {
830
- try {
831
- const tabs = document.getElementById('metadata-tabs');
832
- const button = tabs.shadowRoot.querySelector('button[part=button]');
833
- let changedEvent = null;
834
- tabs.addEventListener('monster-tab-changed', (event) => {
835
- changedEvent = event;
836
- });
1097
+ const tabs = document.getElementById("metadata-tabs");
1098
+ return (
1099
+ tabs instanceof Tabs &&
1100
+ tabs.shadowRoot.querySelector("button[part=button]") !== null
1101
+ );
1102
+ })
1103
+ .then(() => {
1104
+ try {
1105
+ const tabs = document.getElementById("metadata-tabs");
1106
+ const button = tabs.shadowRoot.querySelector("button[part=button]");
1107
+ let changedEvent = null;
1108
+ tabs.addEventListener("monster-tab-changed", (event) => {
1109
+ changedEvent = event;
1110
+ });
837
1111
 
838
- expect(button.getAttribute('data-monster-tab-name')).to.equal('metadata');
839
- expect(button.getAttribute('data-monster-tab-kind')).to.equal('profile');
840
- expect(button.getAttribute('data-monster-tab-priority')).to.equal('10');
841
- expect(button.getAttribute('data-monster-tab-group')).to.equal('main');
1112
+ expect(button.getAttribute("data-monster-tab-name")).to.equal(
1113
+ "metadata",
1114
+ );
1115
+ expect(button.getAttribute("data-monster-tab-kind")).to.equal(
1116
+ "profile",
1117
+ );
1118
+ expect(button.getAttribute("data-monster-tab-priority")).to.equal(
1119
+ "10",
1120
+ );
1121
+ expect(button.getAttribute("data-monster-tab-group")).to.equal(
1122
+ "main",
1123
+ );
842
1124
 
843
- tabs.activeTab('metadata');
844
- expect(changedEvent).to.not.equal(null);
845
- expect(changedEvent.detail.name).to.equal('metadata');
846
- expect(changedEvent.detail.tab).to.equal('metadata');
847
- expect(changedEvent.detail.metadata.kind).to.equal('profile');
848
- done();
849
- } catch (e) {
850
- done(e);
851
- }
852
- }).catch(done);
1125
+ tabs.activeTab("metadata");
1126
+ expect(changedEvent).to.not.equal(null);
1127
+ expect(changedEvent.detail.name).to.equal("metadata");
1128
+ expect(changedEvent.detail.tab).to.equal("metadata");
1129
+ expect(changedEvent.detail.metadata.kind).to.equal("profile");
1130
+ done();
1131
+ } catch (e) {
1132
+ done(e);
1133
+ }
1134
+ })
1135
+ .catch(done);
853
1136
  });
854
1137
 
855
- });
1138
+ it("should reorder tabs by drag and drop without persisting by default", function (done) {
1139
+ let mocks = document.getElementById("mocks");
1140
+ mocks.innerHTML = `
1141
+ <monster-host id="tab-host"></monster-host>
1142
+ `;
1143
+ const host = document.getElementById("tab-host");
1144
+ let savedConfig = null;
1145
+ host.hasConfig = () => Promise.resolve(false);
1146
+ host.getConfig = () => Promise.resolve([]);
1147
+ host.setConfig = (key, value) => {
1148
+ savedConfig = { key, value };
1149
+ return Promise.resolve(value);
1150
+ };
1151
+ host.innerHTML = `
1152
+ <monster-tabs id="reorder-tabs">
1153
+ <div id="first-tab" data-monster-name="first" data-monster-button-label="First">First</div>
1154
+ <div id="second-tab" data-monster-name="second" data-monster-button-label="Second">Second</div>
1155
+ <div id="third-tab" data-monster-name="third" data-monster-button-label="Third">Third</div>
1156
+ </monster-tabs>
1157
+ `;
856
1158
 
1159
+ waitForCondition(() => {
1160
+ const tabs = document.getElementById("reorder-tabs");
1161
+ return (
1162
+ tabs instanceof Tabs &&
1163
+ tabs.shadowRoot.querySelectorAll(
1164
+ 'button[part=button][draggable="true"]',
1165
+ ).length === 3
1166
+ );
1167
+ })
1168
+ .then(() => {
1169
+ const tabs = document.getElementById("reorder-tabs");
1170
+ let orderEvent = null;
1171
+ tabs.addEventListener("monster-tab-order-changed", (event) => {
1172
+ orderEvent = event;
1173
+ });
1174
+
1175
+ dragTab(tabs, "first-tab", "third-tab");
857
1176
 
1177
+ return waitForCondition(() => {
1178
+ return (
1179
+ Array.from(tabs.children)
1180
+ .map((node) => node.id)
1181
+ .join(",") === "second-tab,third-tab,first-tab" &&
1182
+ orderEvent !== null
1183
+ );
1184
+ }).then(() => {
1185
+ try {
1186
+ expect(savedConfig).to.equal(null);
1187
+ expect(orderEvent.detail.order).to.deep.equal([
1188
+ "second",
1189
+ "third",
1190
+ "first",
1191
+ ]);
1192
+ expect(tabs.getOption("features.order.persist")).to.equal(false);
1193
+ done();
1194
+ } catch (e) {
1195
+ done(e);
1196
+ }
1197
+ });
1198
+ })
1199
+ .catch(done);
1200
+ });
1201
+
1202
+ it("should persist and restore tab order when configured", function (done) {
1203
+ let mocks = document.getElementById("mocks");
1204
+ const saved = new Map();
1205
+ mocks.innerHTML = `
1206
+ <monster-host id="persistent-tab-host"></monster-host>
1207
+ `;
1208
+ const host = document.getElementById("persistent-tab-host");
1209
+ host.hasConfig = (key) => Promise.resolve(saved.has(key));
1210
+ host.getConfig = (key) => Promise.resolve(saved.get(key));
1211
+ host.setConfig = (key, value) => {
1212
+ saved.set(key, value);
1213
+ return Promise.resolve(value);
1214
+ };
1215
+ host.innerHTML = `
1216
+ <monster-tabs id="persistent-tabs" data-monster-options='{"features":{"order":{"persist":true}},"config":{"instanceKey":"workspace.tabs"}}'>
1217
+ <div id="persist-first" data-monster-name="first" data-monster-button-label="First">First</div>
1218
+ <div id="persist-second" data-monster-name="second" data-monster-button-label="Second">Second</div>
1219
+ <div id="persist-third" data-monster-name="third" data-monster-button-label="Third">Third</div>
1220
+ </monster-tabs>
1221
+ `;
1222
+
1223
+ waitForCondition(() => {
1224
+ const tabs = document.getElementById("persistent-tabs");
1225
+ return (
1226
+ tabs instanceof Tabs &&
1227
+ tabs.shadowRoot.querySelectorAll("button[part=button]").length === 3
1228
+ );
1229
+ })
1230
+ .then(() => {
1231
+ const tabs = document.getElementById("persistent-tabs");
1232
+ dragTab(tabs, "persist-first", "persist-third");
1233
+
1234
+ return waitForCondition(() => saved.size === 1)
1235
+ .then(() => {
1236
+ const [key, value] = Array.from(saved.entries())[0];
1237
+ expect(key).to.equal("tab_order_tabs_workspace_tabs");
1238
+ expect(value).to.deep.equal(["second", "third", "first"]);
1239
+
1240
+ host.innerHTML = `
1241
+ <monster-tabs id="persistent-tabs-reloaded" data-monster-options='{"features":{"order":{"persist":true}},"config":{"instanceKey":"workspace.tabs"}}'>
1242
+ <div id="reload-first" data-monster-name="first" data-monster-button-label="First">First</div>
1243
+ <div id="reload-second" data-monster-name="second" data-monster-button-label="Second">Second</div>
1244
+ <div id="reload-third" data-monster-name="third" data-monster-button-label="Third">Third</div>
1245
+ </monster-tabs>
1246
+ `;
1247
+
1248
+ return waitForCondition(() => {
1249
+ const reloaded = document.getElementById(
1250
+ "persistent-tabs-reloaded",
1251
+ );
1252
+ return (
1253
+ reloaded instanceof Tabs &&
1254
+ Array.from(reloaded.children)
1255
+ .map((node) => node.id)
1256
+ .join(",") === "reload-second,reload-third,reload-first"
1257
+ );
1258
+ });
1259
+ })
1260
+ .then(() => {
1261
+ done();
1262
+ });
1263
+ })
1264
+ .catch(done);
1265
+ });
1266
+ });
858
1267
  });