tor-browser

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

aria-utils.js (7994B)


      1 /* Utilities related to WAI-ARIA */
      2 
      3 const AriaUtils = {
      4 
      5  /*
      6  Tests simple role assignment: <div role="alert">x</div>
      7  Not intended for nested, context-dependent, or other complex role tests.
      8 
      9  Ex: AriaUtils.assignAndVerifyRolesByRoleNames(["group", "main", "button"])
     10 
     11  */
     12  assignAndVerifyRolesByRoleNames: function(roleNames) {
     13    if (!Array.isArray(roleNames) || !roleNames.length) {
     14      throw `Param roleNames of assignAndVerifyRolesByRoleNames("${roleNames}") should be an array containing at least one role string.`;
     15    }
     16    for (const role of roleNames) {
     17      promise_test(async t => {
     18        let el = document.createElement("div");
     19        el.appendChild(document.createTextNode("x"));
     20        el.setAttribute("role", role); // el.role not yet supported by Gecko.
     21        document.body.appendChild(el);
     22        const computedRole = await test_driver.get_computed_role(el);
     23        assert_equals(computedRole, role.toLowerCase(), el.outerHTML);
     24      }, `role: ${role}`);
     25    }
     26  },
     27 
     28 
     29  /*
     30  Tests computed ROLE of all elements matching selector
     31  against the string value of their data-expectedrole attribute.
     32 
     33  Ex: <div role="list"
     34        data-testname="optional unique test name"
     35        data-expectedrole="list"
     36        class="ex">
     37 
     38      AriaUtils.verifyRolesBySelector(".ex")
     39 
     40  */
     41  verifyRolesBySelector: function(selector, roleTestNamePrefix) {
     42    const els = document.querySelectorAll(selector);
     43    if (!els.length) {
     44      throw `Selector passed in verifyRolesBySelector("${selector}") should match at least one element.`;
     45    }
     46    for (const el of els) {
     47      let role = el.getAttribute("data-expectedrole");
     48      let testName = el.getAttribute("data-testname") || role; // data-testname optional if role is unique per test file
     49      if (typeof roleTestNamePrefix !== "undefined") {
     50        testName = roleTestNamePrefix + testName;
     51      }
     52      promise_test(async t => {
     53        const expectedRole = el.getAttribute("data-expectedrole");
     54        const computedRole = await test_driver.get_computed_role(el);
     55        assert_equals(computedRole, expectedRole, el.outerHTML);
     56      }, `${testName}`);
     57    }
     58  },
     59 
     60 
     61  /*
     62  Tests computed ROLE of selected elements matching selector
     63  against the string value of provided roles array.
     64 
     65  Ex: <foo
     66        data-testname="verify fooRole or barRole role on span"
     67        class="ex-foo-or-bar">
     68 
     69      AriaUtils.verifyRoleOrVariantRolesBySelector(".ex-foo-or-bar", ["fooRole", "barRole"]);
     70 
     71  See also helper function verifyGenericRolesBySelector shorthand of the above using ["generic", "", "none"].
     72 
     73  Note: This function should not be used to circumvent unexpected interop differences in implementations.
     74  It should only be used in specific cases (like "generic") determined by ARIA WG or other spec maintainers to be acceptable for the purposes of testing.
     75 
     76  */
     77  verifyRoleOrVariantRolesBySelector: function(selector, roles) {
     78    const els = document.querySelectorAll(selector);
     79    if (!els.length) {
     80      throw `Selector "${selector}" should match at least one element.`;
     81    }
     82    if (!roles.length || roles.length < 2) {
     83      throw `Roles array ["${roles.join('", "')}"] should include at least two strings, a primary role and at least one acceptable implementation-specific variant. E.g. ["generic", "", "none"]…`;
     84    }
     85    for (const el of els) {
     86      let testName = el.getAttribute("data-testname");
     87      promise_test(async t => {
     88        const expectedRoles = roles;
     89        const computedRole = await test_driver.get_computed_role(el);
     90        for (role of roles){
     91          if (computedRole === role) {
     92            return assert_equals(computedRole, role, `Computed Role: "${computedRole}" matches one of the acceptable role strings in ["${roles.join('", "')}"]: ${el.outerHTML}`);
     93          }
     94        }
     95        return assert_false(true, `Computed Role: "${computedRole}" does not match any of the acceptable role strings in ["${roles.join('", "')}"]: ${el.outerHTML}`);
     96      }, `${testName}`);
     97    }
     98  },
     99 
    100 
    101  /*
    102  Helper function for "generic" ROLE tests.
    103 
    104  Ex: <span
    105        data-testname="verify generic, none, or empty computed role on span"
    106        class="ex-generic">
    107 
    108      AriaUtils.verifyGenericRolesBySelector(".ex-generic");
    109 
    110   This helper function is equivalant to AriaUtils.verifyRoleOrVariantRolesBySelector(".ex-generic", ["generic", "", "none"]);
    111   See various issues and discussions linked from https://github.com/web-platform-tests/interop-accessibility/issues/48
    112 
    113  */
    114  verifyGenericRolesBySelector: function(selector) {
    115    // ARIA WG determined implementation variants "none" (Chromium), and the empty string "" (WebKit), are sufficiently equivalent to "generic" for WPT test verification of HTML-AAM.
    116    // See various discussions linked from https://github.com/web-platform-tests/interop-accessibility/issues/48
    117    this.verifyRoleOrVariantRolesBySelector(selector, ["generic", "", "none"]);
    118  },
    119 
    120 
    121  /*
    122  Tests computed LABEL of all elements matching selector
    123  against the string value of their data-expectedlabel attribute.
    124 
    125  Ex: <div aria-label="foo"
    126        data-testname="optional unique test name"
    127        data-expectedlabel="foo"
    128        class="ex">
    129 
    130      AriaUtils.verifyLabelsBySelector(".ex")
    131 
    132  */
    133  verifyLabelsBySelector: function(selector, labelTestNamePrefix) {
    134    const els = document.querySelectorAll(selector);
    135    if (!els.length) {
    136      throw `Selector passed in verifyLabelsBySelector("${selector}") should match at least one element.`;
    137    }
    138    for (const el of els) {
    139      let label = el.getAttribute("data-expectedlabel");
    140      let testName = el.getAttribute("data-testname") || label; // data-testname optional if label is unique per test file
    141      if (typeof labelTestNamePrefix !== "undefined") {
    142        testName = labelTestNamePrefix + testName;
    143      }
    144      promise_test(async t => {
    145        const expectedLabel = el.getAttribute("data-expectedlabel");
    146        let computedLabel = await test_driver.get_computed_label(el);
    147        assert_not_equals(computedLabel, null, `get_computed_label(el) shouldn't return null for ${el.outerHTML}`);
    148 
    149        // See:
    150        // - https://github.com/w3c/accname/pull/165
    151        // - https://github.com/w3c/accname/issues/192
    152        // - https://github.com/w3c/accname/issues/208
    153        //
    154        // AccName references HTML's definition of ASCII Whitespace
    155        // https://infra.spec.whatwg.org/#ascii-whitespace
    156        // which matches tab (\t), newline (\n), formfeed (\f), return (\r), and regular space (\u0020).
    157        // but it does NOT match non-breaking space (\xA0,\u00A0) and others matched by \s
    158        const asciiWhitespace = /[\t\n\f\r\u0020]+/g;
    159        computedLabel = computedLabel.replace(asciiWhitespace, '\u0020').replace(/^\u0020|\u0020$/g, '');
    160 
    161        assert_equals(computedLabel, expectedLabel, el.outerHTML);
    162      }, `${testName}`);
    163    }
    164  },
    165 
    166 
    167  /*
    168  Tests computed LABEL and ROLE of all elements matching selector using existing
    169    verifyLabelsBySelector(), verifyRolesBySelector() functions and passes a test name prefix
    170    to ensure uniqueness.
    171 
    172  Ex: <div aria-label="foo" role="button"
    173        data-testname="div with role=button is labelled via aria-label"
    174        data-expectedlabel="foo"
    175        data-expectedrole="button"
    176        class="ex-role-and-label">
    177 
    178      AriaUtils.verifyRolesAndLabelsBySelector(".ex-role-and-label")
    179 
    180  */
    181  verifyRolesAndLabelsBySelector: function(selector) {
    182    let labelTestNamePrefix = "Label: ";
    183    let roleTestNamePrefix = "Role: ";
    184    const els = document.querySelectorAll(selector);
    185    if (!els.length) {
    186      throw `Selector passed in verifyRolesAndLabelsBySelector("${selector}") should match at least one element.`;
    187    }
    188    for (const el of els) {
    189      el.classList.add("ex-label-only");
    190      el.classList.add("ex-role-only");
    191    }
    192    this.verifyLabelsBySelector(".ex-label-only", labelTestNamePrefix);
    193    this.verifyRolesBySelector(".ex-role-only", roleTestNamePrefix);
    194  },
    195 };