/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 'use strict'; /* global EVENT_REORDER, EVENT_TEXT_INSERTED */ loadScripts({ name: 'name.js', dir: MOCHITESTS_DIR }); /** * Rules for name tests that are inspired by * accessible/tests/mochitest/name/markuprules.xul * * Each element in the list of rules represents a name calculation rule for a * particular test case. * * The rules have the following format: * { attr } - calculated from attribute * { elm } - calculated from another element * { fromsubtree } - calculated from element's subtree * * * Options include: * * recreated - subrtee is recreated and the test should only continue * after a reorder event * * textchanged - text is inserted into a subtree and the test should only * continue after a text inserted event */ const ARIARule = [{ attr: 'aria-labelledby' }, { attr: 'aria-label' }]; const HTMLControlHeadRule = [...ARIARule, { elm: 'label', isSibling: true }]; const rules = { CSSContent: [{ elm: 'style', isSibling: true }, { fromsubtree: true }], HTMLARIAGridCell: [...ARIARule, { fromsubtree: true }, { attr: 'title' }], HTMLControl: [...HTMLControlHeadRule, { fromsubtree: true }, { attr: 'title' }], HTMLElm: [...ARIARule, { attr: 'title' }], HTMLImg: [...ARIARule, { attr: 'alt', recreated: true }, { attr: 'title' }], HTMLImgEmptyAlt: [...ARIARule, { attr: 'title' }, { attr: 'alt' }], HTMLInputButton: [...HTMLControlHeadRule, { attr: 'value' }, { attr: 'title' }], HTMLInputImage: [...HTMLControlHeadRule, { attr: 'alt', recreated: true }, { attr: 'value', recreated: true }, { attr: 'title' }], HTMLInputImageNoValidSrc: [...HTMLControlHeadRule, { attr: 'alt', recreated: true }, { attr: 'value', recreated: true }], HTMLInputReset: [...HTMLControlHeadRule, { attr: 'value', textchanged: true }], HTMLInputSubmit: [...HTMLControlHeadRule, { attr: 'value', textchanged: true }], HTMLLink: [...ARIARule, { fromsubtree: true }, { attr: 'title' }], HTMLLinkImage: [...ARIARule, { elm: 'img' }, { attr: 'title' }], HTMLOption: [...ARIARule, { attr: 'label' }, { fromsubtree: true }, { attr: 'title' }], HTMLTable: [...ARIARule, { elm: 'caption' }, { attr: 'summary' }, { attr: 'title' }] }; const markupTests = [{ id: 'btn', ruleset: 'HTMLControl', markup: ` test2 test3 `, expected: ['test2 test3', 'test1', 'test4', 'press me', 'test5'] }, { id: 'btn', ruleset: 'HTMLInputButton', markup: ` test2 test3 `, expected: ['test2 test3', 'test1', 'test4', 'name from value', 'name from title'] }, { id: 'btn-submit', ruleset: 'HTMLInputSubmit', markup: ` test2 test3 `, expected: ['test2 test3', 'test1', 'test4', 'name from value'] }, { id: 'btn-reset', ruleset: 'HTMLInputReset', markup: ` test2 test3 `, expected: ['test2 test3', 'test1', 'test4', 'name from value'] }, { id: 'btn-image', ruleset: 'HTMLInputImage', markup: ` test2 test3 `, expected: ['test2 test3', 'test1', 'test4', 'name from alt', 'name from value', 'name from title'] }, { id: 'btn-image', ruleset: 'HTMLInputImageNoValidSrc', markup: ` test2 test3 `, expected: ['test2 test3', 'test1', 'test4', 'name from alt', 'name from value'] }, { id: 'opt', ruleset: 'HTMLOption', markup: ` test2 test3 `, expected: ['test2 test3', 'test1', 'test4', 'option1', 'test5'] }, { id: 'img', ruleset: 'HTMLImg', markup: ` test2 test3 Mozilla logo`, expected: ['test2 test3', 'Logo of Mozilla', 'Mozilla logo', 'This is a logo'] }, { id: 'imgemptyalt', ruleset: 'HTMLImgEmptyAlt', markup: ` test2 test3 `, expected: ['test2 test3', 'Logo of Mozilla', 'This is a logo', ''] }, { id: 'tc', ruleset: 'HTMLElm', markup: ` test2 test3

This is a paragraph

This is a link
  • This is a list
`, expected: ['test2 test3', 'test1', 'test5'] }, { id: 'gc', ruleset: 'HTMLARIAGridCell', markup: ` test2 test3

This is a paragraph

This is a link
  • Listitem1
  • Listitem2
`, expected: ['test2 test3', 'test1', 'This is a paragraph', 'This is a paragraph This is a link This is a list'] }, { id: 't', ruleset: 'HTMLTable', markup: ` lby_tst6_1 lby_tst6_2
caption_tst6
cell1 cell2
`, expected: ['lby_tst6_1 lby_tst6_2', 'arialabel_tst6', 'caption_tst6', 'summary_tst6', 'title_tst6'] }, { id: 'btn', ruleset: 'CSSContent', markup: ` `, expected: ['do not press me', 'press me'] }, { // TODO: uncomment when Bug-1256382 is resoved. // id: 'li', // ruleset: 'CSSContent', // markup: ` // // `, // expected: ['1. Listitem', `${String.fromCharCode(0x2022)} Listitem`] // }, { id: 'a', ruleset: 'HTMLLink', markup: ` test2 test3 test5`, expected: ['test2 test3', 'test1', 'test5', 'test4'] }, { id: 'a-img', ruleset: 'HTMLLinkImage', markup: ` test2 test3 test5`, expected: ['test2 test3', 'test1', 'test5', 'test4'] }]; /** * Wait for an accessible event to happen and, in case given accessible is * defunct, update it to one that is attached to the accessible event. * @param {Promise} onEvent accessible event promise * @param {Object} target { acc, parent, id } structure that contains an * accessible, its parent and its content element * id. */ function* updateAccessibleIfNeeded(onEvent, target) { let event = yield onEvent; if (isDefunct(target.acc)) { target.acc = findAccessibleChildByID(event.accessible, target.id); } } /** * Test accessible name that is calculated from an attribute, remove the * attribute before proceeding to the next name test. If attribute removal * results in a reorder or text inserted event - wait for it. If accessible * becomes defunct, update its reference using the one that is attached to one * of the above events. * @param {Object} browser current "tabbrowser" element * @param {Object} target { acc, parent, id } structure that contains an * accessible, its parent and its content element * id. * @param {Object} rule current attr rule for name calculation * @param {[type]} expected expected name value */ function* testAttrRule(browser, target, rule, expected) { testName(target.acc, expected); let onEvent; if (rule.recreated) { onEvent = waitForEvent(EVENT_REORDER, target.parent); } else if (rule.textchanged) { onEvent = waitForEvent(EVENT_TEXT_INSERTED, target.id); } yield invokeSetAttribute(browser, target.id, rule.attr); if (onEvent) { yield updateAccessibleIfNeeded(onEvent, target); } } /** * Test accessible name that is calculated from an element name, remove the * element before proceeding to the next name test. If element removal results * in a reorder event - wait for it. If accessible becomes defunct, update its * reference using the one that is attached to a possible reorder event. * @param {Object} browser current "tabbrowser" element * @param {Object} target { acc, parent, id } structure that contains an * accessible, its parent and its content element * id. * @param {Object} rule current elm rule for name calculation * @param {[type]} expected expected name value */ function* testElmRule(browser, target, rule, expected) { testName(target.acc, expected); let onEvent = waitForEvent(EVENT_REORDER, rule.isSibling ? target.parent : target.id); yield ContentTask.spawn(browser, rule.elm, elm => content.document.querySelector(`${elm}`).remove()); yield updateAccessibleIfNeeded(onEvent, target); } /** * Test accessible name that is calculated from its subtree, remove the subtree * and wait for a reorder event before proceeding to the next name test. If * accessible becomes defunct, update its reference using the one that is * attached to a reorder event. * @param {Object} browser current "tabbrowser" element * @param {Object} target { acc, parent, id } structure that contains an * accessible, its parent and its content element * id. * @param {Object} rule current subtree rule for name calculation * @param {[type]} expected expected name value */ function* testSubtreeRule(browser, target, rule, expected) { testName(target.acc, expected); let onEvent = waitForEvent(EVENT_REORDER, target.id); yield ContentTask.spawn(browser, target.id, id => { let elm = content.document.getElementById(id); while (elm.firstChild) { elm.removeChild(elm.firstChild); } }); yield updateAccessibleIfNeeded(onEvent, target); } /** * Iterate over a list of rules and test accessible names for each one of the * rules. * @param {Object} browser current "tabbrowser" element * @param {Object} target { acc, parent, id } structure that contains an * accessible, its parent and its content element * id. * @param {Array} ruleset A list of rules to test a target with * @param {Array} expected A list of expected name value for each rule */ function* testNameRule(browser, target, ruleset, expected) { for (let i = 0; i < ruleset.length; ++i) { let rule = ruleset[i]; let testFn; if (rule.attr) { testFn = testAttrRule; } else if (rule.elm) { testFn = testElmRule; } else if (rule.fromsubtree) { testFn = testSubtreeRule; } yield testFn(browser, target, rule, expected[i]); } } markupTests.forEach(({ id, ruleset, markup, expected }) => addAccessibleTask(markup, function*(browser, accDoc) { // Find a target accessible from an accessible subtree. let acc = findAccessibleChildByID(accDoc, id); // Find target's parent accessible from an accessible subtree. let parent = getAccessibleDOMNodeID(acc.parent); let target = { id, parent, acc }; yield testNameRule(browser, target, rules[ruleset], expected); }));