UNPKG

@schukai/monster

Version:

Monster is a simple library for creating fast, robust and lightweight websites.

414 lines (351 loc) 12.4 kB
import {getGlobal} from "../../../../source/types/global.mjs"; import * as chai from 'chai'; import {chaiDom} from "../../../util/chai-dom.mjs"; import {initJSDOM} from "../../../util/jsdom.mjs"; import {ResizeObserverMock} from "../../../util/resize-observer.mjs"; let expect = chai.expect; chai.use(chaiDom); const global = getGlobal(); // language=html let html1 = ` <monster-tabs id="mytabs"> <div data-monster-button-label="TAB1"> <div> Das ist tab 1 </div> </div> <div data-monster-button-label="TAB2"> Das ist tab 2 </div> <div> Das ist tab 3 ohne button </div> <div></div> </monster-tabs> `; // language=html let htmlActiveSecond = ` <monster-tabs id="mytabs"> <div data-monster-button-label="TAB1"> Das ist tab 1 </div> <div data-monster-button-label="TAB2" class="active"> Das ist tab 2 </div> <div data-monster-button-label="TAB3"> Das ist tab 3 </div> </monster-tabs> `; // language=html let htmlOverflow = ` <monster-tabs id="overflow-tabs"> <div data-monster-button-label="Profil">Profil</div> <div data-monster-button-label="Adressen">Adressen</div> <div data-monster-button-label="Kommunikation">Kommunikation</div> <div data-monster-button-label="Beziehungen">Beziehungen</div> <div data-monster-button-label="Anstellungen">Anstellungen</div> <div data-monster-button-label="Einwilligungen">Einwilligungen</div> <div data-monster-button-label="Einwilligungsstatus">Einwilligungsstatus</div> <div data-monster-button-label="Berechtigungen">Berechtigungen</div> <div data-monster-button-label="Dateien">Dateien</div> <div data-monster-button-label="Kommentare">Kommentare</div> <div data-monster-button-label="Wiedervorlagen">Wiedervorlagen</div> </monster-tabs> `; let Tabs; let restoreBoundingClientRect = null; let restoreResizeObserver = null; function waitForCondition(check, {timeout = 4000, interval = 10} = {}) { const startedAt = Date.now(); return new Promise((resolve, reject) => { const tick = () => { try { if (check() === true) { resolve(); return; } } catch (e) { reject(e); return; } if (Date.now() - startedAt > timeout) { reject(new Error('condition timed out')); return; } setTimeout(tick, interval); }; tick(); }); } describe('Tabs', function () { before(function (done) { initJSDOM().then(() => { import("element-internals-polyfill").catch(e => done(e)); let promises = [] if (!global['crypto']) { promises.push(import("@peculiar/webcrypto").then((m) => { const Crypto = m['Crypto']; global['crypto'] = new Crypto(); })); } promises.push(import("../../../../source/components/layout/tabs.mjs").then((m) => { Tabs = m['Tabs']; })) Promise.all(promises).then(()=>{ done(); }).catch(e => done(e)) }); }) describe('document.createElement()', function () { afterEach(() => { if (restoreBoundingClientRect instanceof Function) { restoreBoundingClientRect(); restoreBoundingClientRect = null; } if (restoreResizeObserver instanceof Function) { restoreResizeObserver(); restoreResizeObserver = null; } let mocks = document.getElementById('mocks'); mocks.innerHTML = ""; }) it('should have buttons and tabs', function (done) { let mocks = document.getElementById('mocks'); mocks.innerHTML = html1; setTimeout(() => { try { const tabs = document.getElementById('mytabs') expect(tabs).is.instanceof(Tabs); setTimeout(() => { let nav = tabs.shadowRoot.querySelector('nav'); const buttons = tabs.shadowRoot.querySelectorAll('button[part=button]'); expect(buttons[0]).is.instanceof(HTMLButtonElement); expect(nav.hasChildNodes()).to.be.true; expect(buttons.length).to.be.equal(4); done(); }, 100) } catch (e) { return done(e); } }, 0) }); it('should shorten label from content when no explicit label is provided', function (done) { let mocks = document.getElementById('mocks'); mocks.innerHTML = html1; setTimeout(() => { try { const tabs = document.getElementById('mytabs'); expect(tabs).is.instanceof(Tabs); setTimeout(() => { const thirdTab = tabs.children[2]; expect(thirdTab).to.not.equal(undefined); const reference = thirdTab.getAttribute('id'); expect(reference).to.not.equal(null); const button = tabs.shadowRoot.querySelector( `button[part=button][data-monster-tab-reference="${reference}"]`, ); expect(button).to.not.equal(null); const labelSpan = button.querySelector('span[data-monster-replace]'); expect(labelSpan).to.not.equal(null); expect(labelSpan.textContent.trim()).to.equal('Das ist tab…'); done(); }, 100); } catch (e) { return done(e); } }, 0); }); it('should open the first tab by default when no tab is predefined as active', function (done) { let mocks = document.getElementById('mocks'); mocks.innerHTML = html1; setTimeout(() => { try { const tabs = document.getElementById('mytabs'); expect(tabs).is.instanceof(Tabs); setTimeout(() => { expect(tabs.children[0].classList.contains('active')).to.equal(true); expect(tabs.children[1].classList.contains('active')).to.equal(false); expect(tabs.getActiveTab()).to.equal(tabs.children[0].getAttribute('id')); done(); }, 100); } catch (e) { return done(e); } }, 0); }); it('should keep a predefined active tab instead of opening the first tab', function (done) { let mocks = document.getElementById('mocks'); mocks.innerHTML = htmlActiveSecond; setTimeout(() => { try { const tabs = document.getElementById('mytabs'); expect(tabs).is.instanceof(Tabs); setTimeout(() => { expect(tabs.children[0].classList.contains('active')).to.equal(false); expect(tabs.children[1].classList.contains('active')).to.equal(true); expect(tabs.getActiveTab()).to.equal(tabs.children[1].getAttribute('id')); done(); }, 100); } catch (e) { return done(e); } }, 0); }); it('should configure flip and shift middleware for the overflow popper', function (done) { let mocks = document.getElementById('mocks'); mocks.innerHTML = html1; setTimeout(() => { try { const tabs = document.getElementById('mytabs'); expect(tabs).is.instanceof(Tabs); const modifiers = tabs.getOption('popper.modifiers'); expect(modifiers.some((entry) => entry?.name === 'flip')).to.equal(true); expect(modifiers.some((entry) => entry?.name === 'shift')).to.equal(true); done(); } catch (e) { return done(e); } }, 0); }); it('should move overflowing tabs into the popper without losing them', function (done) { this.timeout(5000); let mocks = document.getElementById('mocks'); const OriginalResizeObserver = window.ResizeObserver; const originalGlobalResizeObserver = global.ResizeObserver; class TrackingResizeObserver extends ResizeObserverMock { static instances = []; static callbackCount = 0; constructor(callback) { super((entries, observer) => { TrackingResizeObserver.callbackCount++; callback(entries, observer); }); TrackingResizeObserver.instances.push(this); } } window.ResizeObserver = TrackingResizeObserver; global.ResizeObserver = TrackingResizeObserver; restoreResizeObserver = () => { window.ResizeObserver = OriginalResizeObserver; global.ResizeObserver = originalGlobalResizeObserver; }; const originalGetBoundingClientRect = global.HTMLElement.prototype.getBoundingClientRect; restoreBoundingClientRect = () => { global.HTMLElement.prototype.getBoundingClientRect = originalGetBoundingClientRect; }; global.HTMLElement.prototype.getBoundingClientRect = function () { const role = this.getAttribute?.('data-monster-role'); const label = this.textContent?.trim() || ''; if (role === 'nav') { return { width: 480, height: 40, top: 0, left: 0, right: 480, bottom: 40, x: 0, y: 0, }; } if (role === 'button') { return { width: Math.max(70, label.length * 11), height: 40, top: 0, left: 0, right: 0, bottom: 40, x: 0, y: 0, }; } return originalGetBoundingClientRect.call(this); }; mocks.innerHTML = htmlOverflow; waitForCondition(() => { const tabs = document.getElementById('overflow-tabs'); const switchButton = tabs?.shadowRoot?.querySelector( '[data-monster-role="switch"]', ); const standardButtons = tabs?.getOption?.('buttons.standard') || []; const popperButtons = tabs?.getOption?.('buttons.popper') || []; return ( tabs instanceof Tabs && switchButton !== null && standardButtons.length > 0 && popperButtons.length > 0 && standardButtons.length + popperButtons.length === 11 && switchButton.classList.contains('hidden') === false ); }).then(() => { try { const tabs = document.getElementById('overflow-tabs'); expect(tabs).is.instanceof(Tabs); const switchButton = tabs.shadowRoot.querySelector( '[data-monster-role="switch"]', ); let standardButtons = tabs.getOption('buttons.standard'); let popperButtons = tabs.getOption('buttons.popper'); expect(switchButton).to.not.equal(null); expect(standardButtons.length).to.be.greaterThan(0); expect(popperButtons.length).to.be.greaterThan(0); expect(standardButtons.length + popperButtons.length).to.equal(11); expect(switchButton.classList.contains('hidden')).to.equal(false); const resizeObserver = TrackingResizeObserver.instances.find( (observer) => observer.elements.includes(tabs.shadowRoot.querySelector('[data-monster-role="nav"]')), ); expect(resizeObserver).to.not.equal(undefined); resizeObserver.triggerResize([]); return waitForCondition(() => { return ( TrackingResizeObserver.callbackCount > 0 && tabs.getOption('buttons.standard').length === 11 && tabs.getOption('buttons.popper').length === 0 ); }).then(() => { return waitForCondition(() => { standardButtons = tabs.getOption('buttons.standard'); popperButtons = tabs.getOption('buttons.popper'); return ( standardButtons.length > 0 && popperButtons.length > 0 && standardButtons.length + popperButtons.length === 11 && new Set( standardButtons .concat(popperButtons) .map((button) => button.reference), ).size === 11 ); }); }).then(() => { restoreBoundingClientRect(); restoreBoundingClientRect = null; restoreResizeObserver(); restoreResizeObserver = null; done(); }); } catch (e) { restoreBoundingClientRect(); restoreBoundingClientRect = null; restoreResizeObserver(); restoreResizeObserver = null; return done(e); } }).catch((e) => { if (restoreBoundingClientRect instanceof Function) { restoreBoundingClientRect(); restoreBoundingClientRect = null; } if (restoreResizeObserver instanceof Function) { restoreResizeObserver(); restoreResizeObserver = null; } done(e); }); }); }); });