@schukai/monster 4.145.3 → 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.
- package/package.json +1 -1
- package/source/components/form/choice-cards.mjs +13 -2
- package/source/components/form/control-bar-spacer.mjs +3 -0
- package/source/components/form/control-bar.mjs +13 -2
- package/source/components/layout/style/tabs.pcss +13 -0
- package/source/components/layout/stylesheet/tabs.mjs +1 -1
- package/source/components/layout/tabs.mjs +399 -10
- package/test/cases/components/layout/tabs.mjs +775 -477
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import {getGlobal} from "../../../../source/types/global.mjs";
|
|
2
|
-
import * as chai from
|
|
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 =
|
|
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(
|
|
199
|
+
reject(new Error("condition timed out"));
|
|
200
200
|
return;
|
|
201
201
|
}
|
|
202
202
|
|
|
@@ -207,7 +207,7 @@ function waitForCondition(check, {timeout = 4000, interval = 10} = {}) {
|
|
|
207
207
|
});
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
function createRect({width, height = 40, x = 0, y = 0}) {
|
|
210
|
+
function createRect({ width, height = 40, x = 0, y = 0 }) {
|
|
211
211
|
return {
|
|
212
212
|
width,
|
|
213
213
|
height,
|
|
@@ -220,24 +220,27 @@ function createRect({width, height = 40, x = 0, y = 0}) {
|
|
|
220
220
|
};
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
function installOverflowTabGeometryMock({
|
|
223
|
+
function installOverflowTabGeometryMock({
|
|
224
|
+
navWidth = 480,
|
|
225
|
+
switchWidth = 44,
|
|
226
|
+
} = {}) {
|
|
224
227
|
const originalGetBoundingClientRect =
|
|
225
228
|
global.HTMLElement.prototype.getBoundingClientRect;
|
|
226
229
|
|
|
227
230
|
global.HTMLElement.prototype.getBoundingClientRect = function () {
|
|
228
|
-
const role = this.getAttribute?.(
|
|
229
|
-
const label = this.textContent?.trim() ||
|
|
231
|
+
const role = this.getAttribute?.("data-monster-role");
|
|
232
|
+
const label = this.textContent?.trim() || "";
|
|
230
233
|
|
|
231
|
-
if (role ===
|
|
232
|
-
return createRect({width: navWidth});
|
|
234
|
+
if (role === "nav") {
|
|
235
|
+
return createRect({ width: navWidth });
|
|
233
236
|
}
|
|
234
237
|
|
|
235
|
-
if (role ===
|
|
236
|
-
return createRect({width: Math.max(70, label.length * 11)});
|
|
238
|
+
if (role === "button") {
|
|
239
|
+
return createRect({ width: Math.max(70, label.length * 11) });
|
|
237
240
|
}
|
|
238
241
|
|
|
239
|
-
if (role ===
|
|
240
|
-
return createRect({width: switchWidth});
|
|
242
|
+
if (role === "switch") {
|
|
243
|
+
return createRect({ width: switchWidth });
|
|
241
244
|
}
|
|
242
245
|
|
|
243
246
|
return originalGetBoundingClientRect.call(this);
|
|
@@ -249,39 +252,84 @@ function installOverflowTabGeometryMock({navWidth = 480, switchWidth = 44} = {})
|
|
|
249
252
|
};
|
|
250
253
|
}
|
|
251
254
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
+
}
|
|
260
276
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
+
}
|
|
267
293
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
VerticalTabs = m['VerticalTabs'];
|
|
273
|
-
}))
|
|
294
|
+
describe("Tabs", function () {
|
|
295
|
+
before(function (done) {
|
|
296
|
+
initJSDOM().then(() => {
|
|
297
|
+
import("element-internals-polyfill").catch((e) => done(e));
|
|
274
298
|
|
|
275
|
-
|
|
276
|
-
done();
|
|
277
|
-
}).catch(e => done(e))
|
|
299
|
+
let promises = [];
|
|
278
300
|
|
|
279
|
-
|
|
280
|
-
|
|
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
|
+
}
|
|
281
309
|
|
|
282
|
-
|
|
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
|
+
});
|
|
283
330
|
|
|
284
|
-
|
|
331
|
+
describe("document.createElement()", function () {
|
|
332
|
+
afterEach(() => {
|
|
285
333
|
if (restoreBoundingClientRect instanceof Function) {
|
|
286
334
|
restoreBoundingClientRect();
|
|
287
335
|
restoreBoundingClientRect = null;
|
|
@@ -290,136 +338,147 @@ describe('Tabs', function () {
|
|
|
290
338
|
restoreResizeObserver();
|
|
291
339
|
restoreResizeObserver = null;
|
|
292
340
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
it('should have buttons and tabs', function (done) {
|
|
298
|
-
|
|
299
|
-
let mocks = document.getElementById('mocks');
|
|
300
|
-
mocks.innerHTML = html1;
|
|
301
|
-
|
|
302
|
-
setTimeout(() => {
|
|
303
|
-
try {
|
|
304
|
-
const tabs = document.getElementById('mytabs')
|
|
305
|
-
expect(tabs).is.instanceof(Tabs);
|
|
306
|
-
|
|
307
|
-
setTimeout(() => {
|
|
308
|
-
let nav = tabs.shadowRoot.querySelector('nav');
|
|
309
|
-
const buttons = tabs.shadowRoot.querySelectorAll('button[part=button]');
|
|
310
|
-
expect(buttons[0]).is.instanceof(HTMLButtonElement);
|
|
311
|
-
expect(nav.hasChildNodes()).to.be.true;
|
|
312
|
-
expect(buttons.length).to.be.equal(4);
|
|
313
|
-
done();
|
|
314
|
-
}, 100)
|
|
315
|
-
|
|
316
|
-
} catch (e) {
|
|
317
|
-
return done(e);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
}, 0)
|
|
341
|
+
let mocks = document.getElementById("mocks");
|
|
342
|
+
mocks.innerHTML = "";
|
|
343
|
+
});
|
|
321
344
|
|
|
345
|
+
it("should have buttons and tabs", function (done) {
|
|
346
|
+
let mocks = document.getElementById("mocks");
|
|
347
|
+
mocks.innerHTML = html1;
|
|
322
348
|
|
|
323
|
-
|
|
349
|
+
setTimeout(() => {
|
|
350
|
+
try {
|
|
351
|
+
const tabs = document.getElementById("mytabs");
|
|
352
|
+
expect(tabs).is.instanceof(Tabs);
|
|
324
353
|
|
|
325
|
-
|
|
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
|
+
});
|
|
326
369
|
|
|
327
|
-
|
|
328
|
-
|
|
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;
|
|
329
373
|
|
|
330
374
|
setTimeout(() => {
|
|
331
375
|
try {
|
|
332
|
-
const tabs = document.getElementById(
|
|
376
|
+
const tabs = document.getElementById("mytabs");
|
|
333
377
|
expect(tabs).is.instanceof(Tabs);
|
|
334
378
|
|
|
335
379
|
setTimeout(() => {
|
|
336
380
|
const thirdTab = tabs.children[2];
|
|
337
381
|
expect(thirdTab).to.not.equal(undefined);
|
|
338
|
-
const reference = thirdTab.getAttribute(
|
|
382
|
+
const reference = thirdTab.getAttribute("id");
|
|
339
383
|
expect(reference).to.not.equal(null);
|
|
340
384
|
const button = tabs.shadowRoot.querySelector(
|
|
341
385
|
`button[part=button][data-monster-tab-reference="${reference}"]`,
|
|
342
386
|
);
|
|
343
387
|
expect(button).to.not.equal(null);
|
|
344
|
-
const labelSpan = button.querySelector(
|
|
388
|
+
const labelSpan = button.querySelector(
|
|
389
|
+
"span[data-monster-replace]",
|
|
390
|
+
);
|
|
345
391
|
expect(labelSpan).to.not.equal(null);
|
|
346
|
-
expect(labelSpan.textContent.trim()).to.equal(
|
|
392
|
+
expect(labelSpan.textContent.trim()).to.equal("Das ist tab…");
|
|
347
393
|
done();
|
|
348
394
|
}, 100);
|
|
349
395
|
} catch (e) {
|
|
350
396
|
return done(e);
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
it('should open the first tab by default when no tab is predefined as active', function (done) {
|
|
397
|
+
}
|
|
398
|
+
}, 0);
|
|
399
|
+
});
|
|
356
400
|
|
|
357
|
-
|
|
358
|
-
|
|
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;
|
|
359
404
|
|
|
360
405
|
setTimeout(() => {
|
|
361
406
|
try {
|
|
362
|
-
const tabs = document.getElementById(
|
|
407
|
+
const tabs = document.getElementById("mytabs");
|
|
363
408
|
expect(tabs).is.instanceof(Tabs);
|
|
364
409
|
|
|
365
410
|
setTimeout(() => {
|
|
366
|
-
expect(tabs.children[0].classList.contains(
|
|
367
|
-
|
|
368
|
-
|
|
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
|
+
);
|
|
369
420
|
done();
|
|
370
421
|
}, 100);
|
|
371
422
|
} catch (e) {
|
|
372
423
|
return done(e);
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
it('should keep a predefined active tab instead of opening the first tab', function (done) {
|
|
424
|
+
}
|
|
425
|
+
}, 0);
|
|
426
|
+
});
|
|
378
427
|
|
|
379
|
-
|
|
380
|
-
|
|
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;
|
|
381
431
|
|
|
382
432
|
setTimeout(() => {
|
|
383
433
|
try {
|
|
384
|
-
const tabs = document.getElementById(
|
|
434
|
+
const tabs = document.getElementById("mytabs");
|
|
385
435
|
expect(tabs).is.instanceof(Tabs);
|
|
386
436
|
|
|
387
437
|
setTimeout(() => {
|
|
388
|
-
expect(tabs.children[0].classList.contains(
|
|
389
|
-
|
|
390
|
-
|
|
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
|
+
);
|
|
391
447
|
done();
|
|
392
448
|
}, 100);
|
|
393
449
|
} catch (e) {
|
|
394
450
|
return done(e);
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
it('should configure flip and shift middleware for the overflow popper', function (done) {
|
|
451
|
+
}
|
|
452
|
+
}, 0);
|
|
453
|
+
});
|
|
400
454
|
|
|
401
|
-
|
|
402
|
-
|
|
455
|
+
it("should configure flip and shift middleware for the overflow popper", function (done) {
|
|
456
|
+
let mocks = document.getElementById("mocks");
|
|
457
|
+
mocks.innerHTML = html1;
|
|
403
458
|
|
|
404
459
|
setTimeout(() => {
|
|
405
460
|
try {
|
|
406
|
-
const tabs = document.getElementById(
|
|
461
|
+
const tabs = document.getElementById("mytabs");
|
|
407
462
|
expect(tabs).is.instanceof(Tabs);
|
|
408
463
|
|
|
409
|
-
const modifiers = tabs.getOption(
|
|
410
|
-
expect(modifiers.some((entry) => entry?.name ===
|
|
411
|
-
|
|
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
|
+
);
|
|
412
471
|
done();
|
|
413
472
|
} catch (e) {
|
|
414
473
|
return done(e);
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
474
|
+
}
|
|
475
|
+
}, 0);
|
|
476
|
+
});
|
|
418
477
|
|
|
419
|
-
it(
|
|
478
|
+
it("should move overflowing tabs into the popper without losing them", function (done) {
|
|
420
479
|
this.timeout(5000);
|
|
421
480
|
|
|
422
|
-
|
|
481
|
+
let mocks = document.getElementById("mocks");
|
|
423
482
|
const OriginalResizeObserver = window.ResizeObserver;
|
|
424
483
|
const originalGlobalResizeObserver = global.ResizeObserver;
|
|
425
484
|
class TrackingResizeObserver extends ResizeObserverMock {
|
|
@@ -442,15 +501,15 @@ describe('Tabs', function () {
|
|
|
442
501
|
};
|
|
443
502
|
restoreBoundingClientRect = installOverflowTabGeometryMock();
|
|
444
503
|
|
|
445
|
-
|
|
504
|
+
mocks.innerHTML = htmlOverflow;
|
|
446
505
|
|
|
447
506
|
waitForCondition(() => {
|
|
448
|
-
const tabs = document.getElementById(
|
|
507
|
+
const tabs = document.getElementById("overflow-tabs");
|
|
449
508
|
const switchButton = tabs?.shadowRoot?.querySelector(
|
|
450
509
|
'[data-monster-role="switch"]',
|
|
451
510
|
);
|
|
452
|
-
const standardButtons = tabs?.getOption?.(
|
|
453
|
-
const popperButtons = tabs?.getOption?.(
|
|
511
|
+
const standardButtons = tabs?.getOption?.("buttons.standard") || [];
|
|
512
|
+
const popperButtons = tabs?.getOption?.("buttons.popper") || [];
|
|
454
513
|
|
|
455
514
|
return (
|
|
456
515
|
tabs instanceof Tabs &&
|
|
@@ -458,108 +517,116 @@ describe('Tabs', function () {
|
|
|
458
517
|
standardButtons.length > 0 &&
|
|
459
518
|
popperButtons.length > 0 &&
|
|
460
519
|
standardButtons.length + popperButtons.length === 11 &&
|
|
461
|
-
switchButton.classList.contains(
|
|
520
|
+
switchButton.classList.contains("hidden") === false
|
|
462
521
|
);
|
|
463
|
-
})
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
expect(switchButton).to.not.equal(null);
|
|
476
|
-
expect(standardButtons.length).to.be.greaterThan(0);
|
|
477
|
-
expect(popperButtons.length).to.be.greaterThan(0);
|
|
478
|
-
expect(standardButtons.length + popperButtons.length).to.equal(11);
|
|
479
|
-
expect(switchButton.classList.contains('hidden')).to.equal(false);
|
|
480
|
-
|
|
481
|
-
const resizeObserver = TrackingResizeObserver.instances.find(
|
|
482
|
-
(observer) => observer.elements.includes(tabs.shadowRoot.querySelector('[data-monster-role="nav"]')),
|
|
483
|
-
);
|
|
484
|
-
expect(resizeObserver).to.not.equal(undefined);
|
|
485
|
-
|
|
486
|
-
originalSetOption = tabs.setOption.bind(tabs);
|
|
487
|
-
let resetAllTabsDuringResize = false;
|
|
488
|
-
tabs.setOption = function (path, value) {
|
|
489
|
-
if (
|
|
490
|
-
path === 'buttons' &&
|
|
491
|
-
value?.standard?.length === 11 &&
|
|
492
|
-
value?.popper?.length === 0
|
|
493
|
-
) {
|
|
494
|
-
resetAllTabsDuringResize = true;
|
|
495
|
-
}
|
|
496
|
-
return originalSetOption(path, value);
|
|
497
|
-
};
|
|
498
|
-
|
|
499
|
-
resizeObserver.triggerResize([]);
|
|
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");
|
|
500
534
|
|
|
501
|
-
|
|
502
|
-
return TrackingResizeObserver.callbackCount > 0;
|
|
503
|
-
}).then(() => {
|
|
504
|
-
return new Promise((resolve) => setTimeout(resolve, 260));
|
|
505
|
-
}).then(() => {
|
|
506
|
-
return new Promise((resolve) => requestAnimationFrame(resolve));
|
|
507
|
-
}).then(() => {
|
|
508
|
-
tabs.setOption = originalSetOption;
|
|
509
|
-
expect(resetAllTabsDuringResize).to.equal(false);
|
|
510
|
-
standardButtons = tabs.getOption('buttons.standard');
|
|
511
|
-
popperButtons = tabs.getOption('buttons.popper');
|
|
535
|
+
expect(switchButton).to.not.equal(null);
|
|
512
536
|
expect(standardButtons.length).to.be.greaterThan(0);
|
|
513
537
|
expect(popperButtons.length).to.be.greaterThan(0);
|
|
514
538
|
expect(standardButtons.length + popperButtons.length).to.equal(11);
|
|
515
|
-
expect(
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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([]);
|
|
563
|
+
|
|
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
|
+
}
|
|
523
602
|
restoreBoundingClientRect();
|
|
524
603
|
restoreBoundingClientRect = null;
|
|
525
604
|
restoreResizeObserver();
|
|
526
605
|
restoreResizeObserver = null;
|
|
527
|
-
done();
|
|
528
|
-
});
|
|
529
|
-
} catch (e) {
|
|
530
|
-
if (tabs && originalSetOption) {
|
|
531
|
-
tabs.setOption = originalSetOption;
|
|
606
|
+
return done(e);
|
|
532
607
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
}
|
|
551
|
-
if (restoreResizeObserver instanceof Function) {
|
|
552
|
-
restoreResizeObserver();
|
|
553
|
-
restoreResizeObserver = null;
|
|
554
|
-
}
|
|
555
|
-
done(e);
|
|
556
|
-
});
|
|
557
|
-
});
|
|
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
|
+
});
|
|
558
625
|
|
|
559
|
-
it(
|
|
626
|
+
it("should preserve the overflow split and recalculate widths while rebuilding tab buttons", async function () {
|
|
560
627
|
this.timeout(5000);
|
|
561
628
|
|
|
562
|
-
const mocks = document.getElementById(
|
|
629
|
+
const mocks = document.getElementById("mocks");
|
|
563
630
|
restoreBoundingClientRect = installOverflowTabGeometryMock();
|
|
564
631
|
|
|
565
632
|
let tabs;
|
|
@@ -569,9 +636,9 @@ describe('Tabs', function () {
|
|
|
569
636
|
mocks.innerHTML = htmlOverflow;
|
|
570
637
|
|
|
571
638
|
await waitForCondition(() => {
|
|
572
|
-
tabs = document.getElementById(
|
|
573
|
-
const standardButtons = tabs?.getOption?.(
|
|
574
|
-
const popperButtons = tabs?.getOption?.(
|
|
639
|
+
tabs = document.getElementById("overflow-tabs");
|
|
640
|
+
const standardButtons = tabs?.getOption?.("buttons.standard") || [];
|
|
641
|
+
const popperButtons = tabs?.getOption?.("buttons.popper") || [];
|
|
575
642
|
|
|
576
643
|
return (
|
|
577
644
|
tabs instanceof Tabs &&
|
|
@@ -581,8 +648,10 @@ describe('Tabs', function () {
|
|
|
581
648
|
);
|
|
582
649
|
});
|
|
583
650
|
|
|
584
|
-
const initialPopperCount = tabs.getOption(
|
|
585
|
-
const standardReference = tabs.getOption(
|
|
651
|
+
const initialPopperCount = tabs.getOption("buttons.popper").length;
|
|
652
|
+
const standardReference = tabs.getOption(
|
|
653
|
+
"buttons.standard.0.reference",
|
|
654
|
+
);
|
|
586
655
|
const panel = document.getElementById(standardReference);
|
|
587
656
|
expect(panel).to.not.equal(null);
|
|
588
657
|
|
|
@@ -590,7 +659,7 @@ describe('Tabs', function () {
|
|
|
590
659
|
let resetAllTabsDuringRebuild = false;
|
|
591
660
|
tabs.setOption = function (path, value) {
|
|
592
661
|
if (
|
|
593
|
-
path ===
|
|
662
|
+
path === "buttons" &&
|
|
594
663
|
value?.standard?.length === 11 &&
|
|
595
664
|
value?.popper?.length === 0
|
|
596
665
|
) {
|
|
@@ -600,32 +669,32 @@ describe('Tabs', function () {
|
|
|
600
669
|
};
|
|
601
670
|
|
|
602
671
|
panel.setAttribute(
|
|
603
|
-
|
|
604
|
-
|
|
672
|
+
"data-monster-button-label",
|
|
673
|
+
"Aktualisierte sehr lange Tab Beschriftung mit viel mehr Platzbedarf",
|
|
605
674
|
);
|
|
606
675
|
|
|
607
676
|
await waitForCondition(() => {
|
|
608
677
|
return tabs
|
|
609
|
-
.getOption(
|
|
610
|
-
.concat(tabs.getOption(
|
|
678
|
+
.getOption("buttons.standard", [])
|
|
679
|
+
.concat(tabs.getOption("buttons.popper", []))
|
|
611
680
|
.some((button) => {
|
|
612
681
|
return (
|
|
613
682
|
button.reference === standardReference &&
|
|
614
|
-
button.label.includes(
|
|
683
|
+
button.label.includes("Aktualisierte")
|
|
615
684
|
);
|
|
616
685
|
});
|
|
617
686
|
});
|
|
618
687
|
await waitForCondition(() => {
|
|
619
|
-
return tabs.getOption(
|
|
688
|
+
return tabs.getOption("buttons.popper").length > initialPopperCount;
|
|
620
689
|
});
|
|
621
690
|
|
|
622
691
|
tabs.setOption = originalSetOption;
|
|
623
692
|
expect(resetAllTabsDuringRebuild).to.equal(false);
|
|
624
|
-
expect(tabs.getOption(
|
|
693
|
+
expect(tabs.getOption("buttons.popper").length).to.be.greaterThan(0);
|
|
625
694
|
expect(
|
|
626
695
|
tabs
|
|
627
|
-
.getOption(
|
|
628
|
-
.concat(tabs.getOption(
|
|
696
|
+
.getOption("buttons.standard")
|
|
697
|
+
.concat(tabs.getOption("buttons.popper")).length,
|
|
629
698
|
).to.equal(11);
|
|
630
699
|
} finally {
|
|
631
700
|
if (tabs && originalSetOption) {
|
|
@@ -636,334 +705,563 @@ describe('Tabs', function () {
|
|
|
636
705
|
}
|
|
637
706
|
});
|
|
638
707
|
|
|
639
|
-
it(
|
|
640
|
-
let mocks = document.getElementById(
|
|
708
|
+
it("should ignore unavailable panels when creating tabs and buttons", function (done) {
|
|
709
|
+
let mocks = document.getElementById("mocks");
|
|
641
710
|
mocks.innerHTML = htmlAvailability;
|
|
642
711
|
|
|
643
712
|
waitForCondition(() => {
|
|
644
|
-
const tabs = document.getElementById(
|
|
713
|
+
const tabs = document.getElementById("availability-tabs");
|
|
645
714
|
return (
|
|
646
715
|
tabs instanceof Tabs &&
|
|
647
|
-
tabs.shadowRoot.querySelectorAll(
|
|
648
|
-
|
|
716
|
+
tabs.shadowRoot.querySelectorAll("button[part=button]").length ===
|
|
717
|
+
2 &&
|
|
718
|
+
tabs.getActiveTab() === "overview"
|
|
649
719
|
);
|
|
650
|
-
})
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
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);
|
|
669
745
|
});
|
|
670
746
|
|
|
671
|
-
it(
|
|
672
|
-
let mocks = document.getElementById(
|
|
747
|
+
it("should ignore unavailable tabs in activeTab and rebuild when they become available", function (done) {
|
|
748
|
+
let mocks = document.getElementById("mocks");
|
|
673
749
|
mocks.innerHTML = htmlAvailability;
|
|
674
750
|
|
|
675
751
|
waitForCondition(() => {
|
|
676
|
-
const tabs = document.getElementById(
|
|
677
|
-
return tabs instanceof Tabs && tabs.getActiveTab() ===
|
|
678
|
-
})
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
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
|
+
});
|
|
685
762
|
|
|
686
|
-
|
|
687
|
-
|
|
763
|
+
tabs.activeTab("vacation");
|
|
764
|
+
expect(tabs.getActiveTab()).to.equal("overview");
|
|
688
765
|
|
|
689
|
-
|
|
766
|
+
vacation.setAttribute("data-monster-tab-available", "true");
|
|
690
767
|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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);
|
|
698
778
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
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);
|
|
707
788
|
});
|
|
708
789
|
|
|
709
|
-
it(
|
|
710
|
-
let mocks = document.getElementById(
|
|
790
|
+
it("should fallback when the active tab becomes unavailable", function (done) {
|
|
791
|
+
let mocks = document.getElementById("mocks");
|
|
711
792
|
mocks.innerHTML = htmlActiveUnavailable;
|
|
712
793
|
|
|
713
794
|
waitForCondition(() => {
|
|
714
|
-
const tabs = document.getElementById(
|
|
715
|
-
return tabs instanceof Tabs && tabs.getActiveTab() ===
|
|
716
|
-
})
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
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);
|
|
731
821
|
});
|
|
732
822
|
|
|
733
|
-
it(
|
|
734
|
-
let mocks = document.getElementById(
|
|
823
|
+
it("should fallback when the active tab is removed directly", function (done) {
|
|
824
|
+
let mocks = document.getElementById("mocks");
|
|
735
825
|
mocks.innerHTML = htmlRemoveFallback;
|
|
736
826
|
|
|
737
827
|
waitForCondition(() => {
|
|
738
|
-
const tabs = document.getElementById(
|
|
828
|
+
const tabs = document.getElementById("remove-fallback-tabs");
|
|
739
829
|
return (
|
|
740
830
|
tabs instanceof Tabs &&
|
|
741
|
-
tabs.getActiveTab() ===
|
|
742
|
-
tabs.shadowRoot.querySelectorAll(
|
|
831
|
+
tabs.getActiveTab() === "second" &&
|
|
832
|
+
tabs.shadowRoot.querySelectorAll("button[part=button]").length === 3
|
|
743
833
|
);
|
|
744
|
-
})
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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);
|
|
759
856
|
});
|
|
760
857
|
|
|
761
|
-
it(
|
|
762
|
-
let mocks = document.getElementById(
|
|
858
|
+
it("should keep disabled tabs visible but not activatable", function (done) {
|
|
859
|
+
let mocks = document.getElementById("mocks");
|
|
763
860
|
mocks.innerHTML = htmlDisabled;
|
|
764
861
|
|
|
765
862
|
waitForCondition(() => {
|
|
766
|
-
const tabs = document.getElementById(
|
|
767
|
-
const disabledPanel = document.getElementById(
|
|
863
|
+
const tabs = document.getElementById("disabled-tabs");
|
|
864
|
+
const disabledPanel = document.getElementById("disabled-panel");
|
|
768
865
|
const disabledButton = tabs?.shadowRoot?.querySelector(
|
|
769
|
-
`button[part=button][data-monster-tab-reference="${disabledPanel?.getAttribute(
|
|
866
|
+
`button[part=button][data-monster-tab-reference="${disabledPanel?.getAttribute("id")}"]`,
|
|
770
867
|
);
|
|
771
868
|
return (
|
|
772
869
|
tabs instanceof Tabs &&
|
|
773
870
|
disabledButton instanceof HTMLButtonElement &&
|
|
774
|
-
tabs.getActiveTab() ===
|
|
871
|
+
tabs.getActiveTab() === "enabled"
|
|
775
872
|
);
|
|
776
|
-
})
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
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
|
+
);
|
|
783
881
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
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);
|
|
793
894
|
});
|
|
794
895
|
|
|
795
|
-
it(
|
|
796
|
-
let mocks = document.getElementById(
|
|
896
|
+
it("should expose refresh, sync, hide and show APIs", function (done) {
|
|
897
|
+
let mocks = document.getElementById("mocks");
|
|
797
898
|
mocks.innerHTML = htmlAvailability;
|
|
798
899
|
|
|
799
900
|
waitForCondition(() => {
|
|
800
|
-
const tabs = document.getElementById(
|
|
801
|
-
return
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
tabs
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
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);
|
|
823
936
|
});
|
|
824
937
|
|
|
825
|
-
it(
|
|
826
|
-
let mocks = document.getElementById(
|
|
938
|
+
it("should not derive labels from content when disabled by option", function (done) {
|
|
939
|
+
let mocks = document.getElementById("mocks");
|
|
827
940
|
mocks.innerHTML = htmlNoDerivedLabel;
|
|
828
941
|
|
|
829
942
|
waitForCondition(() => {
|
|
830
|
-
const tabs = document.getElementById(
|
|
831
|
-
return
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
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);
|
|
843
965
|
});
|
|
844
966
|
|
|
845
|
-
it(
|
|
846
|
-
let mocks = document.getElementById(
|
|
967
|
+
it("should render SVG tab icons as theme-aware masks by default", function (done) {
|
|
968
|
+
let mocks = document.getElementById("mocks");
|
|
847
969
|
mocks.innerHTML = htmlIconModes;
|
|
848
970
|
|
|
849
971
|
waitForCondition(() => {
|
|
850
|
-
const tabs = document.getElementById(
|
|
851
|
-
return
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
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
|
+
);
|
|
858
985
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
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
|
+
);
|
|
864
995
|
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
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);
|
|
869
1002
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
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);
|
|
874
1009
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
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
|
+
);
|
|
879
1018
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1019
|
+
done();
|
|
1020
|
+
} catch (e) {
|
|
1021
|
+
done(e);
|
|
1022
|
+
}
|
|
1023
|
+
})
|
|
1024
|
+
.catch(done);
|
|
885
1025
|
});
|
|
886
1026
|
|
|
887
|
-
it(
|
|
888
|
-
let mocks = document.getElementById(
|
|
1027
|
+
it("should update tab icon mode when the icon mode attribute changes", function (done) {
|
|
1028
|
+
let mocks = document.getElementById("mocks");
|
|
889
1029
|
mocks.innerHTML = htmlIconModes;
|
|
890
1030
|
|
|
891
1031
|
waitForCondition(() => {
|
|
892
|
-
const tabs = document.getElementById(
|
|
893
|
-
return
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
const
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
return
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
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);
|
|
908
1057
|
});
|
|
909
1058
|
|
|
910
|
-
it(
|
|
911
|
-
let mocks = document.getElementById(
|
|
1059
|
+
it("should render vertical tab SVG icons as theme-aware masks by default", function (done) {
|
|
1060
|
+
let mocks = document.getElementById("mocks");
|
|
912
1061
|
mocks.innerHTML = htmlVerticalIconModes;
|
|
913
1062
|
|
|
914
1063
|
waitForCondition(() => {
|
|
915
|
-
const tabs = document.getElementById(
|
|
916
|
-
return
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
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);
|
|
931
1090
|
});
|
|
932
1091
|
|
|
933
|
-
it(
|
|
934
|
-
let mocks = document.getElementById(
|
|
1092
|
+
it("should include stable names and metadata in tab events and buttons", function (done) {
|
|
1093
|
+
let mocks = document.getElementById("mocks");
|
|
935
1094
|
mocks.innerHTML = htmlMetadata;
|
|
936
1095
|
|
|
937
1096
|
waitForCondition(() => {
|
|
938
|
-
const tabs = document.getElementById(
|
|
939
|
-
return
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
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
|
+
});
|
|
948
1111
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
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
|
+
);
|
|
953
1124
|
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
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);
|
|
964
1136
|
});
|
|
965
1137
|
|
|
966
|
-
|
|
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
|
+
`;
|
|
967
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
|
+
});
|
|
968
1174
|
|
|
1175
|
+
dragTab(tabs, "first-tab", "third-tab");
|
|
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
|
+
});
|
|
969
1267
|
});
|