tor-browser

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

browser_aria_errormessage.js (9764B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 /**
      8 * This test verifies that an input with aria-invalid="true/grammar/spelling" exposes the MOX accessible for
      9 * its error message via AXErrorMessageElements.
     10 */
     11 addAccessibleTask(
     12  `
     13  <input id="input-invalid" aria-invalid="true" aria-errormessage="error-msg">
     14  <input id="input-invalid-grammar" aria-invalid="grammar" aria-errormessage="error-msg">
     15  <input id="input-invalid-spelling" aria-invalid="spelling" aria-errormessage="error-msg">
     16  <div id="error-msg">Field validation failed</div>
     17  `,
     18  (_browser, accDoc) => {
     19    const messagesInvalid = getNativeInterface(accDoc, "input-invalid")
     20      .getAttributeValue("AXErrorMessageElements")
     21      .map(e => e.getAttributeValue("AXDOMIdentifier"));
     22 
     23    is(
     24      messagesInvalid.length,
     25      1,
     26      "Only one element referenced via 'aria-errormessage'"
     27    );
     28    is(
     29      messagesInvalid[0],
     30      "error-msg",
     31      "input#input-invalid refers 'error-msg' in the 'aria-errormessage'"
     32    );
     33 
     34    const messagesInvalidGrammar = getNativeInterface(
     35      accDoc,
     36      "input-invalid-grammar"
     37    )
     38      .getAttributeValue("AXErrorMessageElements")
     39      .map(e => e.getAttributeValue("AXDOMIdentifier"));
     40 
     41    is(
     42      messagesInvalidGrammar.length,
     43      1,
     44      "Only one element referenced via 'aria-errormessage'"
     45    );
     46    is(
     47      messagesInvalidGrammar[0],
     48      "error-msg",
     49      "input#input-invalid-grammar refers 'error-msg' in the 'aria-errormessage'"
     50    );
     51 
     52    const messagesInvalidSpelling = getNativeInterface(
     53      accDoc,
     54      "input-invalid-spelling"
     55    )
     56      .getAttributeValue("AXErrorMessageElements")
     57      .map(e => e.getAttributeValue("AXDOMIdentifier"));
     58 
     59    is(
     60      messagesInvalidSpelling.length,
     61      1,
     62      "Only one element referenced via 'aria-errormessage'"
     63    );
     64    is(
     65      messagesInvalidSpelling[0],
     66      "error-msg",
     67      "input#input-invalid-spelling refers 'error-msg' in the 'aria-errormessage'"
     68    );
     69  }
     70 );
     71 
     72 /**
     73 * This test verifies that an input with aria-invalid=true exposes all the MOX accessibles defined through `aria-errormessage`
     74 * via AXErrorMessageElements
     75 */
     76 addAccessibleTask(
     77  `
     78  <label for="input">Field with error</label><input id="input" aria-invalid="true" aria-errormessage="error-msg-specialchar error-msg-10charlong">
     79  <div id="error-msg-specialchar">Field must contain special characters</div>
     80  <div id="error-msg-10charlong">Field must contain more than 10 characters</div>
     81  `,
     82  (_browser, accDoc) => {
     83    let input = getNativeInterface(accDoc, "input");
     84    const errorMessageList = input.getAttributeValue("AXErrorMessageElements");
     85 
     86    let messages = errorMessageList.map(e =>
     87      e.getAttributeValue("AXDOMIdentifier")
     88    );
     89    messages.sort();
     90 
     91    is(
     92      messages.length,
     93      2,
     94      "input#input references two elements via 'aria-errormessage'"
     95    );
     96    is(
     97      messages[0],
     98      "error-msg-10charlong",
     99      "We expect all elements listed in 'aria-errormessage'"
    100    );
    101    is(
    102      messages[1],
    103      "error-msg-specialchar",
    104      "We expect all elements listed in 'aria-errormessage'"
    105    );
    106  }
    107 );
    108 
    109 /**
    110 * When aria-invalid is set to "false", attribute is missing or without a value, AXErrorMessageElements should
    111 * not return associated error messages.
    112 * This test verifies that in this cases, AXErrorMessageElements returns `null`.
    113 */
    114 addAccessibleTask(
    115  `
    116  <label for="input">Field with error</label><input id="input-invalid-false" aria-invalid="false" aria-errormessage="error-msg-specialchar error-msg-10charlong">
    117  <label for="input">Field with error</label><input id="input-invalid-missing" aria-errormessage="error-msg-specialchar error-msg-10charlong">
    118  <label for="input">Field with error</label><input id="input-invalid-spelling-error" aria-invalid aria-errormessage="error-msg-specialchar error-msg-10charlong">
    119  <div id="error-msg-specialchar">Field must contain special characters</div>
    120  <div id="error-msg-10charlong">Field must contain more than 10 characters</div>
    121  `,
    122  (_browser, accDoc) => {
    123    const errorsForInvalidFalse = getNativeInterface(
    124      accDoc,
    125      "input-invalid-false"
    126    ).getAttributeValue("AXErrorMessageElements");
    127 
    128    is(
    129      errorsForInvalidFalse,
    130      null,
    131      "When aria-invalid is 'false', [AXErrorMessageElements] should return null"
    132    );
    133 
    134    const errorsForInvalidMissing = getNativeInterface(
    135      accDoc,
    136      "input-invalid-missing"
    137    ).getAttributeValue("AXErrorMessageElements");
    138 
    139    is(
    140      errorsForInvalidMissing,
    141      null,
    142      "When aria-invalid is missing, [AXErrorMessageElements] should return null"
    143    );
    144 
    145    const errorsForSpellingError = getNativeInterface(
    146      accDoc,
    147      "input-invalid-spelling-error"
    148    ).getAttributeValue("AXErrorMessageElements");
    149 
    150    is(
    151      errorsForSpellingError,
    152      null,
    153      "When aria-invalid is provided without value, [AXErrorMessageElements] should return null"
    154    );
    155  }
    156 );
    157 
    158 /**
    159 * This test modifies the innerText of an associated error message and verifies the correct event AXValidationErrorChagned is fired.
    160 */
    161 addAccessibleTask(
    162  `
    163    <label for="input">Field with error</label><input id="input" aria-invalid="true" aria-errormessage="error-msg">
    164    <div id="error-msg">Field validation failed</div>
    165  `,
    166  async (browser, _accDoc) => {
    167    let validationErrorChanged = waitForMacEvent("AXValidationErrorChanged");
    168    await SpecialPowers.spawn(browser, [], () => {
    169      content.document.getElementById("error-msg").innerText =
    170        "new error message";
    171    });
    172    await validationErrorChanged;
    173    info("validationErrorChanged: event has arrived");
    174  }
    175 );
    176 
    177 /**
    178 * This test modifies the inner tree of an associated error message and verifies the correct event AXValidationErrorChagned is fired.
    179 */
    180 addAccessibleTask(
    181  `
    182    <label for="input">Field with error</label><input id="input" aria-invalid="true" aria-errormessage="error-msg">
    183    <div id="error-msg">Field validation failed <span id="inner-error-msg"></span></div>
    184  `,
    185  async (browser, _accDoc) => {
    186    let validationErrorChanged = waitForMacEvent("AXValidationErrorChanged");
    187 
    188    info("validationErrorChanged: changing inner element");
    189    await SpecialPowers.spawn(browser, [], () => {
    190      content.document.getElementById("inner-error-msg").innerText =
    191        "detailed error message";
    192    });
    193 
    194    await validationErrorChanged;
    195 
    196    info("validationErrorChanged: event has arrived");
    197  }
    198 );
    199 
    200 /**
    201 * When the value of `aria-errormessage` is changed, AXValidationErrorChanged should be triggered.
    202 * The test removes the element id from `aria-errormessage` and checks that:
    203 * - the event was fired
    204 * - AXErrorMessageElements does not return error messages
    205 *
    206 * Then, the test inserts element id back to `aria-errormessage` and checks that:
    207 * - the event AXValidationErrorChanged was fired
    208 * - AXErrorMessageElements contain our error message
    209 */
    210 addAccessibleTask(
    211  `
    212    <label for="input">Field with error</label><input id="input" aria-invalid="true" aria-errormessage="error-msg">
    213    <div id="error-msg">Field validation failed</div>
    214  `,
    215  async (browser, accDoc) => {
    216    let validationErrorChanged = waitForMacEvent("AXValidationErrorChanged");
    217 
    218    info("validationErrorChanged: removing reference to error");
    219    await SpecialPowers.spawn(browser, [], () => {
    220      content.document
    221        .getElementById("input")
    222        .setAttribute("aria-errormessage", "");
    223    });
    224 
    225    await validationErrorChanged;
    226 
    227    info("validationErrorChanged: event has arrived");
    228 
    229    let validationErrors = getNativeInterface(
    230      accDoc,
    231      "input"
    232    ).getAttributeValue("AXErrorMessageElements");
    233 
    234    is(
    235      validationErrors,
    236      null,
    237      "We have removed reference to error message, AXErrorMessageElements should now contain nothing"
    238    );
    239 
    240    info("validationErrorChanged: adding the reference back");
    241    validationErrorChanged = waitForMacEvent("AXValidationErrorChanged");
    242 
    243    await SpecialPowers.spawn(browser, [], () => {
    244      content.document
    245        .getElementById("input")
    246        .setAttribute("aria-errormessage", "error-msg");
    247    });
    248 
    249    await validationErrorChanged;
    250 
    251    validationErrors = getNativeInterface(accDoc, "input")
    252      .getAttributeValue("AXErrorMessageElements")
    253      .map(e => e.getAttributeValue("AXDOMIdentifier"));
    254 
    255    info("validation errors: " + JSON.stringify(validationErrors));
    256 
    257    is(
    258      validationErrors.length,
    259      1,
    260      "Reference to 'error-msg' was returned back"
    261    );
    262    is(
    263      validationErrors[0],
    264      "error-msg",
    265      "Reference to 'error-msg' was returned back"
    266    );
    267  }
    268 );
    269 
    270 /**
    271 * This test modifies the innerText of an associated error message on an
    272 * input with aria-invalid=false and verifies the error change event
    273 * is NOT fired.
    274 */
    275 addAccessibleTask(
    276  `
    277    <label for="input">Valid field with associated error error</label><input id="input" aria-invalid="false" aria-errormessage="error-msg">
    278    <div id="error-msg">Field validation failed</div>
    279  `,
    280  async (browser, _accDoc) => {
    281    // XXX: We don't have a way to await unexpected, non-core events, so we
    282    // use the core EVENT_ERRORMESSAGE_CHANGED here as a proxy for AXValidationErrorChanged
    283    const unexpectedEvents = { unexpected: [[EVENT_ERRORMESSAGE_CHANGED]] };
    284    info("Setting new error message text");
    285    await contentSpawnMutation(browser, unexpectedEvents, function () {
    286      content.document.getElementById("error-msg").innerText =
    287        "new error message";
    288    });
    289    ok(true, "Did not receive error message event!");
    290  }
    291 );