export const testEl = document.createElement("div"); export const containerForInflow = document.createElement("div"); export const containerForAbspos = document.createElement("div"); export const containerForFixed = document.createElement("div"); testEl.id = "test"; containerForInflow.id = "container-for-inflow"; containerForAbspos.id = "container-for-abspos"; containerForFixed.id = "container-for-fixed"; containerForInflow.appendChild(testEl); containerForAbspos.appendChild(containerForInflow); containerForFixed.appendChild(containerForAbspos); document.body.appendChild(containerForFixed); const stylesheet = document.createElement("style"); stylesheet.textContent = ` #container-for-inflow { /* Content area: 100px tall, 200px wide */ height: 100px; width: 200px; padding: 1px 2px; border-width: 2px 4px; margin: 4px 8px; } #container-for-abspos { /* Padding area: 200px tall, 400px wide */ height: 184px; width: 368px; padding: 8px 16px; border-width: 16px 32px; margin: 32px 64px; position: relative; } #container-for-fixed { /* Padding area: 300px tall, 600px wide */ height: 172px; width: 344px; padding: 64px 128px; border-width: 128px 256px; margin: 256px 512px; position: absolute; transform: scale(1); visibility: hidden; } [id ^= container] { border-style: solid; } `; document.head.prepend(stylesheet); function runTestsWithWM(data, testWM, cbWM) { const { style, containingBlockElement, containingBlockArea, preservesPercentages, preservesAuto, canStretchAutoSize, staticPositionX, staticPositionY, } = data; let cbHeight = containingBlockElement ? containingBlockElement.clientHeight : NaN; let cbWidth = containingBlockElement ? containingBlockElement.clientWidth : NaN; if (containingBlockElement && containingBlockArea == "content") { const cs = getComputedStyle(containingBlockElement); cbHeight -= parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom); cbWidth -= parseFloat(cs.paddingLeft) + parseFloat(cs.paddingRight); } const staticPositionTop = cbWM.blockStart == "top" || cbWM.inlineStart == "top" ? staticPositionY : cbHeight - staticPositionY; const staticPositionLeft = cbWM.blockStart == "left" || cbWM.inlineStart == "left" ? staticPositionX : cbWidth - staticPositionX; const staticPositionBottom = cbWM.blockStart == "bottom" || cbWM.inlineStart == "bottom" ? staticPositionY : cbHeight - staticPositionY; const staticPositionRight = cbWM.blockStart == "right" || cbWM.inlineStart == "right" ? staticPositionX : cbWidth - staticPositionX; function serialize(declarations) { return Object.entries(declarations).map(([p, v]) => `${p}: ${v}; `).join(""); } function wmName(wm) { return Object.values(wm.style).join(" "); } function checkStyle(declarations, expected, msg) { test(function() { testEl.style.cssText = style + "; " + serialize(Object.assign({}, declarations, testWM.style)); if (containingBlockElement) { containingBlockElement.style.cssText = serialize(Object.assign({}, cbWM.style)); } const cs = getComputedStyle(testEl); for (let [prop, value] of Object.entries(expected)) { assert_equals(cs[prop], value, `'${prop}'`); } }, `${wmName(testWM)} inside ${wmName(cbWM)} - ${msg}`); testEl.style.cssText = ""; if (containingBlockElement) { containingBlockElement.style.cssText = ""; } } checkStyle({ top: "1px", left: "2px", bottom: "3px", right: "4px", }, { top: "1px", left: "2px", bottom: "3px", right: "4px", }, "Pixels resolve as-is"); checkStyle({ top: "1em", left: "2em", bottom: "3em", right: "4em", "font-size": "10px", }, { top: "10px", left: "20px", bottom: "30px", right: "40px", }, "Relative lengths are absolutized into pixels"); if (preservesPercentages) { checkStyle({ top: "10%", left: "25%", bottom: "50%", right: "75%", }, { top: "10%", left: "25%", bottom: "50%", right: "75%", }, "Percentages resolve as-is"); } else { checkStyle({ top: "10%", left: "25%", bottom: "50%", right: "75%", }, { top: cbHeight * 10 / 100 + "px", left: cbWidth * 25 / 100 + "px", bottom: cbHeight * 50 / 100 + "px", right: cbWidth * 75 / 100 + "px", }, "Percentages are absolutized into pixels"); checkStyle({ top: "calc(10% - 1px)", left: "calc(25% - 2px)", bottom: "calc(50% - 3px)", right: "calc(75% - 4px)", }, { top: cbHeight * 10 / 100 - 1 + "px", left: cbWidth * 25 / 100 - 2 + "px", bottom: cbHeight * 50 / 100 - 3 + "px", right: cbWidth * 75 / 100 - 4 + "px", }, "calc() is absolutized into pixels"); } if (canStretchAutoSize) { // Force overconstraintment by setting size or with insets that would result in // negative size. Then the resolved value should be the computed one according to // https://drafts.csswg.org/cssom/#resolved-value-special-case-property-like-top checkStyle({ top: "1px", left: "2px", bottom: "3px", right: "4px", height: "0px", width: "0px", }, { top: "1px", left: "2px", bottom: "3px", right: "4px", }, "Pixels resolve as-is when overconstrained"); checkStyle({ top: "100%", left: "100%", bottom: "100%", right: "100%", }, { top: cbHeight + "px", left: cbWidth + "px", bottom: cbHeight + "px", right: cbWidth + "px", }, "Percentages absolutize the computed value when overconstrained"); } if (preservesAuto) { checkStyle({ top: "auto", left: "auto", bottom: "3px", right: "4px", }, { top: "auto", left: "auto", bottom: "3px", right: "4px", }, "If start side is 'auto' and end side is not, 'auto' resolves as-is"); checkStyle({ top: "1px", left: "2px", bottom: "auto", right: "auto", }, { top: "1px", left: "2px", bottom: "auto", right: "auto", }, "If end side is 'auto' and start side is not, 'auto' resolves as-is"); checkStyle({ top: "auto", left: "auto", bottom: "auto", right: "auto", }, { top: "auto", left: "auto", bottom: "auto", right: "auto", }, "If opposite sides are 'auto', they resolve as-is"); } else if (canStretchAutoSize) { checkStyle({ top: "auto", left: "auto", bottom: "3px", right: "4px", }, { top: cbHeight - 3 + "px", left: cbWidth - 4 + "px", bottom: "3px", right: "4px", }, "If start side is 'auto' and end side is not, 'auto' resolves to used value"); checkStyle({ top: "1px", left: "2px", bottom: "auto", right: "auto", }, { top: "1px", left: "2px", bottom: cbHeight - 1 + "px", right: cbWidth - 2 + "px", }, "If end side is 'auto' and start side is not, 'auto' resolves to used value"); checkStyle({ top: "auto", left: "auto", bottom: "auto", right: "auto", }, { top: staticPositionTop + "px", left: staticPositionLeft + "px", bottom: staticPositionBottom + "px", right: staticPositionRight + "px", }, "If opposite sides are 'auto', they resolve to used value"); } else { checkStyle({ top: "auto", left: "auto", bottom: "3px", right: "4px", }, { top: "-3px", left: "-4px", bottom: "3px", right: "4px", }, "If start side is 'auto' and end side is not, 'auto' resolves to used value"); checkStyle({ top: "1px", left: "2px", bottom: "auto", right: "auto", }, { top: "1px", left: "2px", bottom: "-1px", right: "-2px", }, "If end side is 'auto' and start side is not, 'auto' resolves to used value"); checkStyle({ top: "auto", left: "auto", bottom: "auto", right: "auto", }, { top: "0px", left: "0px", bottom: "0px", right: "0px", }, "If opposite sides are 'auto', they resolve to used value"); } } const writingModes = [{ style: { "writing-mode": "horizontal-tb", "direction": "ltr", }, blockStart: "top", blockEnd: "bottom", inlineStart: "left", inlineEnd: "right", }, { style: { "writing-mode": "horizontal-tb", "direction": "rtl", }, blockStart: "top", blockEnd: "bottom", inlineStart: "right", inlineEnd: "left", }, { style: { "writing-mode": "vertical-lr", "direction": "ltr", }, blockStart: "left", blockEnd: "right", inlineStart: "top", inlineEnd: "bottom", }, { style: { "writing-mode": "vertical-lr", "direction": "rtl", }, blockStart: "left", blockEnd: "right", inlineStart: "bottom", inlineEnd: "top", }, { style: { "writing-mode": "vertical-rl", "direction": "ltr", }, blockStart: "right", blockEnd: "left", inlineStart: "top", inlineEnd: "bottom", }, { style: { "writing-mode": "vertical-rl", "direction": "rtl", }, blockStart: "right", blockEnd: "left", inlineStart: "bottom", inlineEnd: "top", }]; export function runTests(data) { for (let testWM of writingModes) { for (let cbWM of writingModes) { runTestsWithWM(data, testWM, cbWM); } } }