UNPKG

@schukai/monster

Version:

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

402 lines (340 loc) 11 kB
import * as chai from "chai"; import { initJSDOM } from "../../../util/jsdom.mjs"; let expect = chai.expect; let resolveClippingBoundaryElement; let applyAdaptiveFloatingElementSize; describe("form floating-ui boundary resolution", function () { before(function (done) { initJSDOM() .then(() => { return import( "../../../../source/components/form/util/floating-ui.mjs" ); }) .then((m) => { resolveClippingBoundaryElement = m.resolveClippingBoundaryElement; applyAdaptiveFloatingElementSize = m.applyAdaptiveFloatingElementSize; done(); }) .catch((e) => done(e)); }); afterEach(() => { const mocks = document.getElementById("mocks"); mocks.innerHTML = ""; }); it("should ignore parent popper content wrappers as clipping boundaries for nested controls", function () { const mocks = document.getElementById("mocks"); const wrapper = document.createElement("div"); const popperHost = document.createElement("div"); const selectHost = document.createElement("div"); wrapper.style.overflow = "hidden"; mocks.appendChild(wrapper); wrapper.appendChild(popperHost); const popperShadow = popperHost.attachShadow({ mode: "open" }); popperShadow.innerHTML = ` <div data-monster-role="popper"> <div part="content" data-monster-overflow-mode="both" style="overflow: auto;"> </div> </div> `; popperShadow.querySelector('[part="content"]').appendChild(selectHost); const selectShadow = selectHost.attachShadow({ mode: "open" }); selectShadow.innerHTML = ` <div data-monster-role="control"></div> <div data-monster-role="popper"></div> `; const control = selectShadow.querySelector('[data-monster-role="control"]'); const popper = selectShadow.querySelector('[data-monster-role="popper"]'); expect(resolveClippingBoundaryElement(control, popper)).to.equal(wrapper); }); it("should adapt the content max height to the available popper height", function () { const mocks = document.getElementById("mocks"); const popper = document.createElement("div"); const header = document.createElement("div"); const content = document.createElement("div"); const footer = document.createElement("div"); popper.style.maxHeight = "300px"; content.style.maxHeight = "240px"; content.setAttribute("part", "content"); popper.appendChild(header); popper.appendChild(content); popper.appendChild(footer); mocks.appendChild(popper); header.getBoundingClientRect = () => { return { width: 180, height: 30, top: 0, left: 0, right: 180, bottom: 30, x: 0, y: 0, }; }; popper.getBoundingClientRect = () => { return { width: 220, height: 200, top: 0, left: 0, right: 220, bottom: 200, x: 0, y: 0, }; }; footer.getBoundingClientRect = () => { return { width: 180, height: 30, top: 0, left: 0, right: 180, bottom: 30, x: 0, y: 0, }; }; content.getBoundingClientRect = () => { return { width: 180, height: 140, top: 0, left: 0, right: 180, bottom: 140, x: 0, y: 0, }; }; applyAdaptiveFloatingElementSize(popper, { availableWidth: 220, availableHeight: 160, }); expect(popper.style.maxHeight).to.equal("160px"); expect(content.style.maxWidth).to.equal(""); expect(content.style.maxHeight).to.equal("100px"); }); it("should not clamp the floating element height when content overflow is visible", function () { const mocks = document.getElementById("mocks"); const popper = document.createElement("div"); const content = document.createElement("div"); popper.style.maxHeight = "300px"; content.style.maxHeight = "240px"; content.setAttribute("part", "content"); content.setAttribute("data-monster-overflow-mode", "visible"); popper.appendChild(content); mocks.appendChild(popper); applyAdaptiveFloatingElementSize(popper, { availableWidth: 220, availableHeight: 160, }); expect(popper.style.maxHeight).to.equal(""); expect(content.style.maxHeight).to.equal("240px"); }); it("should keep at least one readable line for scrollable content", function () { const mocks = document.getElementById("mocks"); const popper = document.createElement("div"); const content = document.createElement("div"); popper.style.maxHeight = "300px"; content.setAttribute("part", "content"); content.textContent = "A long help text that still needs one readable line."; content.style.fontSize = "16px"; content.style.lineHeight = "24px"; popper.appendChild(content); mocks.appendChild(popper); applyAdaptiveFloatingElementSize(popper, { availableWidth: 220, availableHeight: 10, }); expect(content.style.maxHeight).to.equal("24px"); }); it("should use the first slotted element line height for the minimum readable size", function () { const mocks = document.getElementById("mocks"); const popperHost = document.createElement("div"); const slottedParagraph = document.createElement("p"); slottedParagraph.textContent = "Readable help line"; slottedParagraph.style.lineHeight = "26px"; mocks.appendChild(popperHost); const shadowRoot = popperHost.attachShadow({ mode: "open" }); shadowRoot.innerHTML = ` <div data-monster-role="popper"> <div part="content"> <slot></slot> </div> </div> `; const popper = shadowRoot.querySelector('[data-monster-role="popper"]'); const content = shadowRoot.querySelector('[part="content"]'); popperHost.appendChild(slottedParagraph); applyAdaptiveFloatingElementSize(popper, { availableWidth: 220, availableHeight: 10, }); expect(content.style.maxHeight).to.equal("26px"); }); it("should respect a smaller nested scroll container height", function () { const mocks = document.getElementById("mocks"); const popper = document.createElement("div"); const content = document.createElement("div"); const options = document.createElement("div"); content.setAttribute("part", "content"); content.style.overflowY = "hidden"; options.style.overflowY = "auto"; options.style.height = "72px"; options.style.maxHeight = "72px"; Object.defineProperty(options, "scrollHeight", { configurable: true, value: 72, }); content.appendChild(options); popper.appendChild(content); mocks.appendChild(popper); applyAdaptiveFloatingElementSize(popper, { availableWidth: 220, availableHeight: 180, }); expect(content.style.maxHeight).to.equal("180px"); expect(options.style.height).to.equal("72px"); expect(options.style.maxHeight).to.equal("72px"); }); it("should keep a preferred-width popper stable instead of growing with content", function () { const mocks = document.getElementById("mocks"); const popper = document.createElement("div"); const header = document.createElement("div"); const content = document.createElement("div"); const options = document.createElement("div"); popper.dataset.monsterPreferredWidth = "240"; popper.dataset.monsterWidthBehavior = "preferred"; popper.style.maxWidth = "600px"; content.setAttribute("part", "content"); options.style.overflowY = "auto"; Object.defineProperty(content, "scrollWidth", { configurable: true, value: 620, }); Object.defineProperty(options, "scrollWidth", { configurable: true, value: 620, }); header.getBoundingClientRect = () => { return { width: 240, height: 24, top: 0, left: 0, right: 240, bottom: 24, x: 0, y: 0, }; }; content.getBoundingClientRect = () => { return { width: 220, height: 120, top: 0, left: 0, right: 220, bottom: 120, x: 0, y: 0, }; }; popper.getBoundingClientRect = () => { return { width: 240, height: 160, top: 0, left: 0, right: 240, bottom: 160, x: 0, y: 0, }; }; content.appendChild(options); popper.appendChild(header); popper.appendChild(content); mocks.appendChild(popper); applyAdaptiveFloatingElementSize(popper, { availableWidth: 600, availableHeight: 200, }); expect(popper.style.width).to.equal("240px"); expect(popper.style.minWidth).to.equal("240px"); }); it("should constrain horizontal overlay-aware content on the inline axis", function () { const mocks = document.getElementById("mocks"); const popper = document.createElement("div"); const content = document.createElement("div"); content.setAttribute("part", "content"); content.setAttribute("data-monster-overflow-mode", "horizontal"); Object.defineProperty(content, "scrollHeight", { configurable: true, value: 120, }); popper.appendChild(content); mocks.appendChild(popper); applyAdaptiveFloatingElementSize(popper, { availableWidth: 240, availableHeight: 200, }); expect(content.style.overflowX).to.equal("auto"); expect(content.style.overflowY).to.equal("visible"); expect(content.style.height).to.equal(""); expect(content.style.maxHeight).to.equal("200px"); }); it("should preserve declared nested scroll height when horizontal overlay content fits", function () { const mocks = document.getElementById("mocks"); const popper = document.createElement("div"); const content = document.createElement("div"); const options = document.createElement("div"); content.setAttribute("part", "content"); content.setAttribute("data-monster-overflow-mode", "horizontal"); options.style.overflowY = "auto"; options.style.height = "72px"; options.style.maxHeight = "72px"; Object.defineProperty(content, "scrollHeight", { configurable: true, value: 120, }); Object.defineProperty(options, "scrollHeight", { configurable: true, value: 72, }); content.appendChild(options); popper.appendChild(content); mocks.appendChild(popper); applyAdaptiveFloatingElementSize(popper, { availableWidth: 240, availableHeight: 200, }); expect(options.style.height).to.equal("72px"); expect(options.style.maxHeight).to.equal("72px"); }); it("should constrain horizontal overlay-aware content height only when it exceeds available height", function () { const mocks = document.getElementById("mocks"); const popper = document.createElement("div"); const content = document.createElement("div"); content.setAttribute("part", "content"); content.setAttribute("data-monster-overflow-mode", "horizontal"); Object.defineProperty(content, "scrollHeight", { configurable: true, value: 320, }); popper.appendChild(content); mocks.appendChild(popper); applyAdaptiveFloatingElementSize(popper, { availableWidth: 240, availableHeight: 200, }); expect(content.style.overflowX).to.equal("auto"); expect(content.style.overflowY).to.equal("auto"); expect(content.style.height).to.equal("200px"); expect(content.style.maxHeight).to.equal("200px"); }); });