@schukai/monster
Version:
Monster is a simple library for creating fast, robust and lightweight websites.
402 lines (340 loc) • 11 kB
JavaScript
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");
});
});