tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test-box-properties.js (13064B)


      1 import {
      2  testElement,
      3  writingModes,
      4  testCSSValues,
      5  testComputedValues,
      6  makeDeclaration
      7 } from "./test-shared.js";
      8 
      9 // Values to use while testing
     10 const testValues = {
     11  "length": ["1px", "2px", "3px", "4px", "5px"],
     12  "color": ["rgb(1, 1, 1)", "rgb(2, 2, 2)", "rgb(3, 3, 3)", "rgb(4, 4, 4)", "rgb(5, 5, 5)"],
     13  "border-style": ["solid", "dashed", "dotted", "double", "groove"],
     14 };
     15 
     16 /**
     17 * Creates a group of physical and logical box properties, such as
     18 *
     19 * { physical: {
     20 *     left: "margin-left", right: "margin-right",
     21 *     top: "margin-top", bottom: "margin-bottom",
     22 *   }, logical: {
     23 *     inlineStart: "margin-inline-start", inlineEnd: "margin-inline-end",
     24 *     blockStart: "margin-block-start", blockEnd: "margin-block-end",
     25 *   }, shorthands: {
     26 *     "margin": ["margin-top", "margin-right", "margin-bottom", "margin-left"],
     27 *     "margin-inline": ["margin-inline-start", "margin-inline-end"],
     28 *     "margin-block": ["margin-block-start", "margin-block-end"],
     29 *   }, type: ["length"], prerequisites: "...", property: "margin-*" }
     30 *
     31 * @param {string} property
     32 *        A string representing the property names, like "margin-*".
     33 * @param {Object} descriptor
     34 * @param {string|string[]} descriptor.type
     35 *        Describes the kind of values accepted by the property, like "length".
     36 *        Must be a key or a collection of keys from the `testValues` object.
     37 * @param {Object={}} descriptor.prerequisites
     38 *        Represents property declarations that are needed by `property` to work.
     39 *        For example, border-width properties require a border style.
     40 */
     41 export function createBoxPropertyGroup(property, descriptor) {
     42  const logical = {};
     43  const physical = {};
     44  const shorthands = {};
     45  for (const axis of ["inline", "block"]) {
     46    const shorthand = property.replace("*", axis);
     47    const longhands = [];
     48    shorthands[shorthand] = longhands;
     49    for (const side of ["start", "end"]) {
     50      const logicalSide = axis + "-" + side;
     51      const camelCase = logicalSide.replace(/-(.)/g, (match, $1) => $1.toUpperCase());
     52      const longhand = property.replace("*", logicalSide);
     53      logical[camelCase] = longhand;
     54      longhands.push(longhand);
     55    }
     56  }
     57  const isInset = property === "inset-*";
     58  let prerequisites = "";
     59  for (const physicalSide of ["left", "right", "top", "bottom"]) {
     60    physical[physicalSide] = isInset ? physicalSide : property.replace("*", physicalSide);
     61    prerequisites += makeDeclaration(descriptor.prerequisites, physicalSide);
     62  }
     63  shorthands[property.replace("-*", "")] =
     64    ["top", "right", "bottom", "left"].map(physicalSide => physical[physicalSide]);
     65  const type = [].concat(descriptor.type);
     66  return {logical, physical, shorthands, type, prerequisites, property};
     67 }
     68 
     69 /**
     70 * Creates a group physical and logical box-corner properties.
     71 *
     72 * @param {string} property
     73 *        A string representing the property names, like "border-*-radius".
     74 * @param {Object} descriptor
     75 * @param {string|string[]} descriptor.type
     76 *        Describes the kind of values accepted by the property, like "length".
     77 *        Must be a key or a collection of keys from the `testValues` object.
     78 * @param {Object={}} descriptor.prerequisites
     79 *        Represents property declarations that are needed by `property` to work.
     80 *        For example, border-width properties require a border style.
     81 */
     82 export function createCornerPropertyGroup(property, descriptor) {
     83  const logical = {};
     84  const physical = {};
     85  const shorthands = {};
     86  for (const logicalCorner of ["start-start", "start-end", "end-start", "end-end"]) {
     87    const prop = property.replace("*", logicalCorner);
     88    const [block_side, inline_side] = logicalCorner.split("-");
     89    const b = "block" + block_side.charAt(0).toUpperCase() + block_side.slice(1);
     90    const i = "inline" + inline_side.charAt(0).toUpperCase() + inline_side.slice(1);
     91    const index = b + "-" + i; // e.g. "blockStart-inlineEnd"
     92    logical[index] = prop;
     93  }
     94  let prerequisites = "";
     95  for (const physicalCorner of ["top-left", "top-right", "bottom-left", "bottom-right"]) {
     96    const prop = property.replace("*", physicalCorner);
     97    physical[physicalCorner] = prop;
     98    prerequisites += makeDeclaration(descriptor.prerequisites, physicalCorner);
     99  }
    100  const type = [].concat(descriptor.type);
    101  return {logical, physical, shorthands, type, prerequisites, property};
    102 }
    103 
    104 /**
    105 * Creates a group of physical and logical sizing properties.
    106 *
    107 * @param {string} prefix
    108 *        One of "", "max-" or "min-".
    109 */
    110 export function createSizingPropertyGroup(prefix) {
    111  return {
    112    logical: {
    113      inline: `${prefix}inline-size`,
    114      block: `${prefix}block-size`,
    115    },
    116    physical: {
    117      horizontal: `${prefix}width`,
    118      vertical: `${prefix}height`,
    119    },
    120    type: ["length"],
    121    prerequisites: makeDeclaration({display: "block"}),
    122    property: (prefix ? prefix.slice(0, -1) + " " : "") + "sizing",
    123  };
    124 }
    125 
    126 /**
    127 * Tests a grup of logical and physical properties in different writing modes.
    128 *
    129 * @param {Object} group
    130 *        An object returned by createBoxPropertyGroup or createSizingPropertyGroup.
    131 */
    132 export function runTests(group) {
    133  const values = testValues[group.type[0]].map(function(_, i) {
    134    return group.type.map(type => testValues[type][i]).join(" ");
    135  });
    136  const logicals = Object.values(group.logical);
    137  const physicals = Object.values(group.physical);
    138  const shorthands = group.shorthands ? Object.entries(group.shorthands) : null;
    139  const is_corner = group.property == "border-*-radius";
    140 
    141  test(function() {
    142    const expected = [];
    143    for (const [i, logicalProp] of logicals.entries()) {
    144      testElement.style.setProperty(logicalProp, values[i]);
    145      expected.push([logicalProp, values[i]]);
    146    }
    147    testCSSValues("logical properties in inline style", testElement.style, expected);
    148  }, `Test that logical ${group.property} properties are supported.`);
    149  testElement.style.cssText = "";
    150 
    151  const shorthandValues = {};
    152  for (const [shorthand, longhands] of shorthands || []) {
    153    let valueArray;
    154    if (group.type.length > 1) {
    155      valueArray = [values[0]];
    156    } else {
    157      valueArray = testValues[group.type].slice(0, longhands.length);
    158    }
    159    shorthandValues[shorthand] = valueArray;
    160    const value = valueArray.join(" ");
    161    const expected = [[shorthand, value]];
    162    for (let [i, longhand] of longhands.entries()) {
    163      expected.push([longhand, valueArray[group.type.length > 1 ? 0 : i]]);
    164    }
    165    test(function() {
    166      testElement.style.setProperty(shorthand, value);
    167      testCSSValues("shorthand in inline style", testElement.style, expected);
    168      const stylesheet = `.test { ${group.prerequisites} }`;
    169      testComputedValues("shorthand in computed style", stylesheet, expected);
    170    }, `Test that ${shorthand} shorthand sets longhands and serializes correctly.`);
    171    testElement.style.cssText = "";
    172  }
    173 
    174  for (const writingMode of writingModes) {
    175    for (const style of writingMode.styles) {
    176      const writingModeDecl = makeDeclaration(style);
    177 
    178      const associated = {};
    179      for (const [logicalSide, logicalProp] of Object.entries(group.logical)) {
    180        let physicalProp;
    181        if (is_corner) {
    182          const [ block_side, inline_side] = logicalSide.split("-");
    183          const physicalSide1 = writingMode[block_side];
    184          const physicalSide2 = writingMode[inline_side];
    185          let physicalCorner;
    186          // mirror "left-top" to "top-left" etc
    187          if (["top", "bottom"].includes(physicalSide1)) {
    188            physicalCorner = physicalSide1 + "-" + physicalSide2;
    189          } else {
    190            physicalCorner = physicalSide2 + "-" + physicalSide1;
    191          }
    192          physicalProp = group.physical[physicalCorner];
    193        } else {
    194          physicalProp = group.physical[writingMode[logicalSide]];
    195        }
    196        associated[logicalProp] = physicalProp;
    197        associated[physicalProp] = logicalProp;
    198      }
    199 
    200      // Test that logical properties are converted to their physical
    201      // equivalent correctly when all in the group are present on a single
    202      // declaration, with no overwriting of previous properties and
    203      // no physical properties present.  We put the writing mode properties
    204      // on a separate declaration to test that the computed values of these
    205      // properties are used, rather than those on the same declaration.
    206      test(function() {
    207        let decl = group.prerequisites;
    208        const expected = [];
    209        for (const [i, logicalProp] of logicals.entries()) {
    210          decl += `${logicalProp}: ${values[i]}; `;
    211          expected.push([logicalProp, values[i]]);
    212          expected.push([associated[logicalProp], values[i]]);
    213        }
    214        testComputedValues("logical properties on one declaration, writing " +
    215                           `mode properties on another, '${writingModeDecl}'`,
    216                           `.test { ${writingModeDecl} } .test { ${decl} }`,
    217                           expected);
    218      }, `Test that logical ${group.property} properties share computed values `
    219       + `with their physical associates, with '${writingModeDecl}'.`);
    220 
    221      // Test logical shorthand properties.
    222      if (shorthands) {
    223        test(function() {
    224          for (const [shorthand, longhands] of shorthands) {
    225            let valueArray = shorthandValues[shorthand];
    226            const decl = group.prerequisites + `${shorthand}: ${valueArray.join(" ")}; `;
    227            const expected = [];
    228            for (let [i, longhand] of longhands.entries()) {
    229              const longhandValue = valueArray[group.type.length > 1 ? 0 : i];
    230              expected.push([longhand, longhandValue]);
    231              expected.push([associated[longhand], longhandValue]);
    232            }
    233            testComputedValues("shorthand properties on one declaration, writing " +
    234                               `mode properties on another, '${writingModeDecl}'`,
    235                               `.test { ${writingModeDecl} } .test { ${decl} }`,
    236                               expected);
    237          }
    238        }, `Test that ${group.property} shorthands set the computed value of both `
    239         + `logical and physical longhands, with '${writingModeDecl}'.`);
    240      }
    241 
    242      // Test that logical and physical properties are cascaded together,
    243      // honoring their relative order on a single declaration
    244      // (a) with a single logical property after the physical ones
    245      // (b) with a single physical property after the logical ones
    246      test(function() {
    247        for (const lastIsLogical of [true, false]) {
    248          const lasts = lastIsLogical ? logicals : physicals;
    249          const others = lastIsLogical ? physicals : logicals;
    250          for (const lastProp of lasts) {
    251            let decl = writingModeDecl + group.prerequisites;
    252            const expected = [];
    253            for (const [i, prop] of others.entries()) {
    254              decl += `${prop}: ${values[i]}; `;
    255              const valueIdx = associated[prop] === lastProp ? others.length : i;
    256              expected.push([prop, values[valueIdx]]);
    257              expected.push([associated[prop], values[valueIdx]]);
    258            }
    259            decl += `${lastProp}: ${values[others.length]}; `;
    260            testComputedValues(`'${lastProp}' last on single declaration, '${writingModeDecl}'`,
    261                               `.test { ${decl} }`,
    262                               expected);
    263          }
    264        }
    265      }, `Test that ${group.property} properties honor order of appearance when both `
    266       + `logical and physical associates are declared, with '${writingModeDecl}'.`);
    267 
    268      // Test that logical and physical properties are cascaded properly when
    269      // on different declarations
    270      // (a) with a logical property in the high specificity rule
    271      // (b) with a physical property in the high specificity rule
    272      test(function() {
    273        for (const highIsLogical of [true, false]) {
    274          let lowDecl = writingModeDecl + group.prerequisites;
    275          const high = highIsLogical ? logicals : physicals;
    276          const others = highIsLogical ? physicals : logicals;
    277          for (const [i, prop] of others.entries()) {
    278            lowDecl += `${prop}: ${values[i]}; `;
    279          }
    280          for (const highProp of high) {
    281            const highDecl = `${highProp}: ${values[others.length]}; `;
    282            const expected = [];
    283            for (const [i, prop] of others.entries()) {
    284              const valueIdx = associated[prop] === highProp ? others.length : i;
    285              expected.push([prop, values[valueIdx]]);
    286              expected.push([associated[prop], values[valueIdx]]);
    287            }
    288            testComputedValues(`'${highProp}', two declarations, '${writingModeDecl}'`,
    289                               `#test { ${highDecl} } .test { ${lowDecl} }`,
    290                               expected);
    291          }
    292        }
    293      }, `Test that ${group.property} properties honor selector specificty when both `
    294       + `logical and physical associates are declared, with '${writingModeDecl}'.`);
    295    }
    296  }
    297 }