diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /testing/web-platform/tests/css/css-logical/resources | |
parent | Initial commit. (diff) | |
download | firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/css/css-logical/resources')
3 files changed, 436 insertions, 0 deletions
diff --git a/testing/web-platform/tests/css/css-logical/resources/test-box-properties.js b/testing/web-platform/tests/css/css-logical/resources/test-box-properties.js new file mode 100644 index 0000000000..ef1854f97d --- /dev/null +++ b/testing/web-platform/tests/css/css-logical/resources/test-box-properties.js @@ -0,0 +1,297 @@ +import { + testElement, + writingModes, + testCSSValues, + testComputedValues, + makeDeclaration +} from "./test-shared.js"; + +// Values to use while testing +const testValues = { + "length": ["1px", "2px", "3px", "4px", "5px"], + "color": ["rgb(1, 1, 1)", "rgb(2, 2, 2)", "rgb(3, 3, 3)", "rgb(4, 4, 4)", "rgb(5, 5, 5)"], + "border-style": ["solid", "dashed", "dotted", "double", "groove"], +}; + +/** + * Creates a group of physical and logical box properties, such as + * + * { physical: { + * left: "margin-left", right: "margin-right", + * top: "margin-top", bottom: "margin-bottom", + * }, logical: { + * inlineStart: "margin-inline-start", inlineEnd: "margin-inline-end", + * blockStart: "margin-block-start", blockEnd: "margin-block-end", + * }, shorthands: { + * "margin": ["margin-top", "margin-right", "margin-bottom", "margin-left"], + * "margin-inline": ["margin-inline-start", "margin-inline-end"], + * "margin-block": ["margin-block-start", "margin-block-end"], + * }, type: ["length"], prerequisites: "...", property: "margin-*" } + * + * @param {string} property + * A string representing the property names, like "margin-*". + * @param {Object} descriptor + * @param {string|string[]} descriptor.type + * Describes the kind of values accepted by the property, like "length". + * Must be a key or a collection of keys from the `testValues` object. + * @param {Object={}} descriptor.prerequisites + * Represents property declarations that are needed by `property` to work. + * For example, border-width properties require a border style. + */ +export function createBoxPropertyGroup(property, descriptor) { + const logical = {}; + const physical = {}; + const shorthands = {}; + for (const axis of ["inline", "block"]) { + const shorthand = property.replace("*", axis); + const longhands = []; + shorthands[shorthand] = longhands; + for (const side of ["start", "end"]) { + const logicalSide = axis + "-" + side; + const camelCase = logicalSide.replace(/-(.)/g, (match, $1) => $1.toUpperCase()); + const longhand = property.replace("*", logicalSide); + logical[camelCase] = longhand; + longhands.push(longhand); + } + } + const isInset = property === "inset-*"; + let prerequisites = ""; + for (const physicalSide of ["left", "right", "top", "bottom"]) { + physical[physicalSide] = isInset ? physicalSide : property.replace("*", physicalSide); + prerequisites += makeDeclaration(descriptor.prerequisites, physicalSide); + } + shorthands[property.replace("-*", "")] = + ["top", "right", "bottom", "left"].map(physicalSide => physical[physicalSide]); + const type = [].concat(descriptor.type); + return {logical, physical, shorthands, type, prerequisites, property}; +} + +/** + * Creates a group physical and logical box-corner properties. + * + * @param {string} property + * A string representing the property names, like "border-*-radius". + * @param {Object} descriptor + * @param {string|string[]} descriptor.type + * Describes the kind of values accepted by the property, like "length". + * Must be a key or a collection of keys from the `testValues` object. + * @param {Object={}} descriptor.prerequisites + * Represents property declarations that are needed by `property` to work. + * For example, border-width properties require a border style. + */ +export function createCornerPropertyGroup(property, descriptor) { + const logical = {}; + const physical = {}; + const shorthands = {}; + for (const logicalCorner of ["start-start", "start-end", "end-start", "end-end"]) { + const prop = property.replace("*", logicalCorner); + const [block_side, inline_side] = logicalCorner.split("-"); + const b = "block" + block_side.charAt(0).toUpperCase() + block_side.slice(1); + const i = "inline" + inline_side.charAt(0).toUpperCase() + inline_side.slice(1); + const index = b + "-" + i; // e.g. "blockStart-inlineEnd" + logical[index] = prop; + } + let prerequisites = ""; + for (const physicalCorner of ["top-left", "top-right", "bottom-left", "bottom-right"]) { + const prop = property.replace("*", physicalCorner); + physical[physicalCorner] = prop; + prerequisites += makeDeclaration(descriptor.prerequisites, physicalCorner); + } + const type = [].concat(descriptor.type); + return {logical, physical, shorthands, type, prerequisites, property}; +} + +/** + * Creates a group of physical and logical sizing properties. + * + * @param {string} prefix + * One of "", "max-" or "min-". + */ +export function createSizingPropertyGroup(prefix) { + return { + logical: { + inline: `${prefix}inline-size`, + block: `${prefix}block-size`, + }, + physical: { + horizontal: `${prefix}width`, + vertical: `${prefix}height`, + }, + type: ["length"], + prerequisites: makeDeclaration({display: "block"}), + property: (prefix ? prefix.slice(0, -1) + " " : "") + "sizing", + }; +} + +/** + * Tests a grup of logical and physical properties in different writing modes. + * + * @param {Object} group + * An object returned by createBoxPropertyGroup or createSizingPropertyGroup. + */ +export function runTests(group) { + const values = testValues[group.type[0]].map(function(_, i) { + return group.type.map(type => testValues[type][i]).join(" "); + }); + const logicals = Object.values(group.logical); + const physicals = Object.values(group.physical); + const shorthands = group.shorthands ? Object.entries(group.shorthands) : null; + const is_corner = group.property == "border-*-radius"; + + test(function() { + const expected = []; + for (const [i, logicalProp] of logicals.entries()) { + testElement.style.setProperty(logicalProp, values[i]); + expected.push([logicalProp, values[i]]); + } + testCSSValues("logical properties in inline style", testElement.style, expected); + }, `Test that logical ${group.property} properties are supported.`); + testElement.style.cssText = ""; + + const shorthandValues = {}; + for (const [shorthand, longhands] of shorthands || []) { + let valueArray; + if (group.type.length > 1) { + valueArray = [values[0]]; + } else { + valueArray = testValues[group.type].slice(0, longhands.length); + } + shorthandValues[shorthand] = valueArray; + const value = valueArray.join(" "); + const expected = [[shorthand, value]]; + for (let [i, longhand] of longhands.entries()) { + expected.push([longhand, valueArray[group.type.length > 1 ? 0 : i]]); + } + test(function() { + testElement.style.setProperty(shorthand, value); + testCSSValues("shorthand in inline style", testElement.style, expected); + const stylesheet = `.test { ${group.prerequisites} }`; + testComputedValues("shorthand in computed style", stylesheet, expected); + }, `Test that ${shorthand} shorthand sets longhands and serializes correctly.`); + testElement.style.cssText = ""; + } + + for (const writingMode of writingModes) { + for (const style of writingMode.styles) { + const writingModeDecl = makeDeclaration(style); + + const associated = {}; + for (const [logicalSide, logicalProp] of Object.entries(group.logical)) { + let physicalProp; + if (is_corner) { + const [ block_side, inline_side] = logicalSide.split("-"); + const physicalSide1 = writingMode[block_side]; + const physicalSide2 = writingMode[inline_side]; + let physicalCorner; + // mirror "left-top" to "top-left" etc + if (["top", "bottom"].includes(physicalSide1)) { + physicalCorner = physicalSide1 + "-" + physicalSide2; + } else { + physicalCorner = physicalSide2 + "-" + physicalSide1; + } + physicalProp = group.physical[physicalCorner]; + } else { + physicalProp = group.physical[writingMode[logicalSide]]; + } + associated[logicalProp] = physicalProp; + associated[physicalProp] = logicalProp; + } + + // Test that logical properties are converted to their physical + // equivalent correctly when all in the group are present on a single + // declaration, with no overwriting of previous properties and + // no physical properties present. We put the writing mode properties + // on a separate declaration to test that the computed values of these + // properties are used, rather than those on the same declaration. + test(function() { + let decl = group.prerequisites; + const expected = []; + for (const [i, logicalProp] of logicals.entries()) { + decl += `${logicalProp}: ${values[i]}; `; + expected.push([logicalProp, values[i]]); + expected.push([associated[logicalProp], values[i]]); + } + testComputedValues("logical properties on one declaration, writing " + + `mode properties on another, '${writingModeDecl}'`, + `.test { ${writingModeDecl} } .test { ${decl} }`, + expected); + }, `Test that logical ${group.property} properties share computed values ` + + `with their physical associates, with '${writingModeDecl}'.`); + + // Test logical shorthand properties. + if (shorthands) { + test(function() { + for (const [shorthand, longhands] of shorthands) { + let valueArray = shorthandValues[shorthand]; + const decl = group.prerequisites + `${shorthand}: ${valueArray.join(" ")}; `; + const expected = []; + for (let [i, longhand] of longhands.entries()) { + const longhandValue = valueArray[group.type.length > 1 ? 0 : i]; + expected.push([longhand, longhandValue]); + expected.push([associated[longhand], longhandValue]); + } + testComputedValues("shorthand properties on one declaration, writing " + + `mode properties on another, '${writingModeDecl}'`, + `.test { ${writingModeDecl} } .test { ${decl} }`, + expected); + } + }, `Test that ${group.property} shorthands set the computed value of both ` + + `logical and physical longhands, with '${writingModeDecl}'.`); + } + + // Test that logical and physical properties are cascaded together, + // honoring their relative order on a single declaration + // (a) with a single logical property after the physical ones + // (b) with a single physical property after the logical ones + test(function() { + for (const lastIsLogical of [true, false]) { + const lasts = lastIsLogical ? logicals : physicals; + const others = lastIsLogical ? physicals : logicals; + for (const lastProp of lasts) { + let decl = writingModeDecl + group.prerequisites; + const expected = []; + for (const [i, prop] of others.entries()) { + decl += `${prop}: ${values[i]}; `; + const valueIdx = associated[prop] === lastProp ? others.length : i; + expected.push([prop, values[valueIdx]]); + expected.push([associated[prop], values[valueIdx]]); + } + decl += `${lastProp}: ${values[others.length]}; `; + testComputedValues(`'${lastProp}' last on single declaration, '${writingModeDecl}'`, + `.test { ${decl} }`, + expected); + } + } + }, `Test that ${group.property} properties honor order of appearance when both ` + + `logical and physical associates are declared, with '${writingModeDecl}'.`); + + // Test that logical and physical properties are cascaded properly when + // on different declarations + // (a) with a logical property in the high specificity rule + // (b) with a physical property in the high specificity rule + test(function() { + for (const highIsLogical of [true, false]) { + let lowDecl = writingModeDecl + group.prerequisites; + const high = highIsLogical ? logicals : physicals; + const others = highIsLogical ? physicals : logicals; + for (const [i, prop] of others.entries()) { + lowDecl += `${prop}: ${values[i]}; `; + } + for (const highProp of high) { + const highDecl = `${highProp}: ${values[others.length]}; `; + const expected = []; + for (const [i, prop] of others.entries()) { + const valueIdx = associated[prop] === highProp ? others.length : i; + expected.push([prop, values[valueIdx]]); + expected.push([associated[prop], values[valueIdx]]); + } + testComputedValues(`'${highProp}', two declarations, '${writingModeDecl}'`, + `#test { ${highDecl} } .test { ${lowDecl} }`, + expected); + } + } + }, `Test that ${group.property} properties honor selector specificty when both ` + + `logical and physical associates are declared, with '${writingModeDecl}'.`); + } + } +} diff --git a/testing/web-platform/tests/css/css-logical/resources/test-logical-values.js b/testing/web-platform/tests/css/css-logical/resources/test-logical-values.js new file mode 100644 index 0000000000..7a039379cb --- /dev/null +++ b/testing/web-platform/tests/css/css-logical/resources/test-logical-values.js @@ -0,0 +1,27 @@ +import { + testElement, + writingModes, + testCSSValues, + testComputedValues, + makeDeclaration +} from "./test-shared.js"; + +/** + * Tests flow-relative values for a CSS property in different writing modes. + * + * @param {string} property + * The CSS property to be tested. + * @param {string[]} values + * An array with the flow-relative values to be tested. + */ +export function runTests(property, values) { + for (const value of values) { + test(function() { + const {style} = testElement; + style.cssText = ""; + style.setProperty(property, value); + testCSSValues("logical values in inline style", style, [[property, value]]); + testComputedValues("logical values in computed style", style, [[property, value]]); + }, `Test that '${property}: ${value}' is supported.`); + } +} diff --git a/testing/web-platform/tests/css/css-logical/resources/test-shared.js b/testing/web-platform/tests/css/css-logical/resources/test-shared.js new file mode 100644 index 0000000000..7a1da2e649 --- /dev/null +++ b/testing/web-platform/tests/css/css-logical/resources/test-shared.js @@ -0,0 +1,112 @@ +const sheet = document.head.appendChild(document.createElement("style")); + +// Specify size for outer <div> to avoid unconstrained-size warnings +// when writing-mode of the inner test <div> is vertical-* +const wrapper = document.body.appendChild(document.createElement("div")); +wrapper.style.cssText = "width:100px; height: 100px;"; +export const testElement = wrapper.appendChild(document.createElement("div")); +testElement.id = testElement.className = "test"; + +// Six unique overall writing modes for property-mapping purposes. +export const writingModes = [ + { + styles: [ + {"writing-mode": "horizontal-tb", "direction": "ltr"}, + ], + blockStart: "top", blockEnd: "bottom", inlineStart: "left", inlineEnd: "right", + over: "top", under: "bottom", lineLeft: "left", lineRight: "right", + block: "vertical", inline: "horizontal" }, + { + styles: [ + {"writing-mode": "horizontal-tb", "direction": "rtl"}, + ], + blockStart: "top", blockEnd: "bottom", inlineStart: "right", inlineEnd: "left", + over: "top", under: "bottom", lineLeft: "left", lineRight: "right", + block: "vertical", inline: "horizontal" }, + { + styles: [ + {"writing-mode": "vertical-rl", "direction": "rtl"}, + {"writing-mode": "sideways-rl", "direction": "rtl"}, + ], + blockStart: "right", blockEnd: "left", inlineStart: "bottom", inlineEnd: "top", + over: "right", under: "left", lineLeft: "top", lineRight: "bottom", + block: "horizontal", inline: "vertical" }, + { + styles: [ + {"writing-mode": "vertical-rl", "direction": "ltr"}, + {"writing-mode": "sideways-rl", "direction": "ltr"}, + ], + blockStart: "right", blockEnd: "left", inlineStart: "top", inlineEnd: "bottom", + over: "right", under: "left", lineLeft: "top", lineRight: "bottom", + block: "horizontal", inline: "vertical" }, + { + styles: [ + {"writing-mode": "vertical-lr", "direction": "rtl"}, + ], + blockStart: "left", blockEnd: "right", inlineStart: "bottom", inlineEnd: "top", + over: "right", under: "left", lineLeft: "top", lineRight: "bottom", + block: "horizontal", inline: "vertical" }, + { + styles: [ + {"writing-mode": "sideways-lr", "direction": "ltr"}, + ], + blockStart: "left", blockEnd: "right", inlineStart: "bottom", inlineEnd: "top", + over: "left", under: "right", lineLeft: "bottom", lineRight: "top", + block: "horizontal", inline: "vertical" }, + { + styles: [ + {"writing-mode": "vertical-lr", "direction": "ltr"}, + ], + blockStart: "left", blockEnd: "right", inlineStart: "top", inlineEnd: "bottom", + over: "right", under: "left", lineLeft: "top", lineRight: "bottom", + block: "horizontal", inline: "vertical" }, + { + styles: [ + {"writing-mode": "sideways-lr", "direction": "rtl"}, + ], + blockStart: "left", blockEnd: "right", inlineStart: "top", inlineEnd: "bottom", + over: "left", under: "right", lineLeft: "bottom", lineRight: "top", + block: "horizontal", inline: "vertical" }, +]; + +// Check if logical properties work well in WebKit non-standard +// '-webkit-writing-mode: horizontal-bt' mode +if (CSS.supports("-webkit-writing-mode", "horizontal-bt")) { + writingModes.push ( + { + styles: [ + {"-webkit-writing-mode": "horizontal-bt", "direction": "ltr"}, + ], + blockStart: "bottom", blockEnd: "top", inlineStart: "left", inlineEnd: "right", + over: "top", under: "bottom", lineLeft: "left", lineRight: "right", + block: "vertical", inline: "horizontal" }, + { + styles: [ + {"-webkit-writing-mode": "horizontal-bt", "direction": "rtl"}, + ], + blockStart: "bottom", blockEnd: "top", inlineStart: "right", inlineEnd: "left", + over: "top", under: "bottom", lineLeft: "left", lineRight: "right", + block: "vertical", inline: "horizontal" }, + ) +} + +export function testCSSValues(testName, style, expectedValues) { + for (const [property, value] of expectedValues) { + assert_equals(style.getPropertyValue(property), value, `${testName}, ${property}`); + } +} + +export function testComputedValues(testName, rules, expectedValues) { + sheet.textContent = rules; + const cs = getComputedStyle(testElement); + testCSSValues(testName, cs, expectedValues); + sheet.textContent = ""; +} + +export function makeDeclaration(object = {}, replacement = "*") { + let decl = ""; + for (const [property, value] of Object.entries(object)) { + decl += `${property.replace("*", replacement)}: ${value}; `; + } + return decl; +} |