diff options
Diffstat (limited to '')
-rw-r--r-- | accessible/tests/browser/text/head.js | 276 |
1 files changed, 276 insertions, 0 deletions
diff --git a/accessible/tests/browser/text/head.js b/accessible/tests/browser/text/head.js new file mode 100644 index 0000000000..fa4b095892 --- /dev/null +++ b/accessible/tests/browser/text/head.js @@ -0,0 +1,276 @@ +/* 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"; + +/* exported createTextLeafPoint, DIRECTION_NEXT, DIRECTION_PREVIOUS, + BOUNDARY_FLAG_DEFAULT, BOUNDARY_FLAG_INCLUDE_ORIGIN, + BOUNDARY_FLAG_STOP_IN_EDITABLE, BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER, + readablePoint, testPointEqual, textBoundaryGenerator, testBoundarySequence, + isFinalValueCorrect, isFinalValueCorrect, testInsertText, testDeleteText, + testCopyText, testPasteText, testCutText, testSetTextContents */ + +// Load the shared-head file first. +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js", + this +); + +// Loading and common.js from accessible/tests/mochitest/ for all tests, as +// well as promisified-events.js. + +/* import-globals-from ../../mochitest/role.js */ + +loadScripts( + { name: "common.js", dir: MOCHITESTS_DIR }, + { name: "text.js", dir: MOCHITESTS_DIR }, + { name: "role.js", dir: MOCHITESTS_DIR }, + { name: "promisified-events.js", dir: MOCHITESTS_DIR } +); + +const DIRECTION_NEXT = Ci.nsIAccessibleTextLeafPoint.DIRECTION_NEXT; +const DIRECTION_PREVIOUS = Ci.nsIAccessibleTextLeafPoint.DIRECTION_PREVIOUS; + +const BOUNDARY_FLAG_DEFAULT = + Ci.nsIAccessibleTextLeafPoint.BOUNDARY_FLAG_DEFAULT; +const BOUNDARY_FLAG_INCLUDE_ORIGIN = + Ci.nsIAccessibleTextLeafPoint.BOUNDARY_FLAG_INCLUDE_ORIGIN; +const BOUNDARY_FLAG_STOP_IN_EDITABLE = + Ci.nsIAccessibleTextLeafPoint.BOUNDARY_FLAG_STOP_IN_EDITABLE; +const BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER = + Ci.nsIAccessibleTextLeafPoint.BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER; + +function createTextLeafPoint(acc, offset) { + let accService = Cc["@mozilla.org/accessibilityService;1"].getService( + nsIAccessibilityService + ); + + return accService.createTextLeafPoint(acc, offset); +} + +// Converts an nsIAccessibleTextLeafPoint into a human/machine +// readable tuple with a readable accessible and the offset within it. +// For a point text leaf it would look like this: ["hello", 2], +// For a point in an empty input it would look like this ["input#name", 0] +function readablePoint(point) { + const readableLeaf = acc => { + let tagName = getAccessibleTagName(acc); + if (tagName && !tagName.startsWith("_moz_generated")) { + let domNodeID = getAccessibleDOMNodeID(acc); + if (domNodeID) { + return `${tagName}#${domNodeID}`; + } + return tagName; + } + + return acc.name; + }; + + return [readableLeaf(point.accessible), point.offset]; +} + +function sequenceEqual(val, expected, msg) { + Assert.deepEqual(val, expected, msg); +} + +// eslint-disable-next-line camelcase +function sequenceEqualTodo(val, expected, msg) { + todo_is(JSON.stringify(val), JSON.stringify(expected), msg); +} + +function pointsEqual(pointA, pointB) { + return ( + pointA.offset == pointB.offset && pointA.accessible == pointB.accessible + ); +} + +function testPointEqual(pointA, pointB, msg) { + is(pointA.offset, pointB.offset, `Offset mismatch - ${msg}`); + is(pointA.accessible, pointB.accessible, `Accessible mismatch - ${msg}`); +} + +function* textBoundaryGenerator( + firstPoint, + boundaryType, + direction, + flags = BOUNDARY_FLAG_DEFAULT +) { + // Our start point should be inclusive of the given point. + let nextLeafPoint = firstPoint.findBoundary( + boundaryType, + direction, + flags | BOUNDARY_FLAG_INCLUDE_ORIGIN + ); + let textLeafPoint = null; + + do { + textLeafPoint = nextLeafPoint; + yield textLeafPoint; + nextLeafPoint = textLeafPoint.findBoundary(boundaryType, direction, flags); + } while (!pointsEqual(textLeafPoint, nextLeafPoint)); +} + +// This function takes FindBoundary arguments and an expected sequence +// of boundary points formatted with readablePoint. +// For example, word starts would look like this: +// [["one two", 0], ["one two", 4], ["one two", 7]] +function testBoundarySequence( + startPoint, + boundaryType, + direction, + expectedSequence, + msg, + options = {} +) { + let sequence = [ + ...textBoundaryGenerator( + startPoint, + boundaryType, + direction, + options.flags ? options.flags : BOUNDARY_FLAG_DEFAULT + ), + ]; + (options.todo ? sequenceEqualTodo : sequenceEqual)( + sequence.map(readablePoint), + expectedSequence, + msg + ); +} + +/////////////////////////////////////////////////////////////////////////////// +// Editable text + +async function waitForCopy(browser) { + await BrowserTestUtils.waitForContentEvent(browser, "copy", false, evt => { + return true; + }); + + let clipboardData = await invokeContentTask(browser, [], async () => { + let text = await content.navigator.clipboard.readText(); + return text; + }); + + return clipboardData; +} + +async function isFinalValueCorrect( + browser, + acc, + expectedTextLeafs, + msg = "Value is correct" +) { + let value = + acc.role == ROLE_ENTRY + ? acc.value + : await invokeContentTask(browser, [], () => { + return content.document.body.textContent; + }); + + let [before, text, after] = expectedTextLeafs; + let finalValue = + before && after && !text + ? [before, after].join(" ") + : [before, text, after].join(""); + + is(value.replace("\xa0", " "), finalValue, msg); +} + +function waitForTextChangeEvents(acc, eventSeq) { + let events = eventSeq.map(eventType => { + return [eventType, acc]; + }); + + if (acc.role == ROLE_ENTRY) { + events.push([EVENT_TEXT_VALUE_CHANGE, acc]); + } + + return waitForEvents(events); +} + +async function testSetTextContents(acc, text, staticContentOffset, events) { + acc.QueryInterface(nsIAccessibleEditableText); + let evtPromise = waitForTextChangeEvents(acc, events); + acc.setTextContents(text); + let evt = (await evtPromise)[0]; + evt.QueryInterface(nsIAccessibleTextChangeEvent); + is(evt.start, staticContentOffset); +} + +async function testInsertText( + acc, + textToInsert, + insertOffset, + staticContentOffset +) { + acc.QueryInterface(nsIAccessibleEditableText); + + let evtPromise = waitForTextChangeEvents(acc, [EVENT_TEXT_INSERTED]); + acc.insertText(textToInsert, staticContentOffset + insertOffset); + let evt = (await evtPromise)[0]; + evt.QueryInterface(nsIAccessibleTextChangeEvent); + is(evt.start, staticContentOffset + insertOffset); +} + +async function testDeleteText( + acc, + startOffset, + endOffset, + staticContentOffset +) { + acc.QueryInterface(nsIAccessibleEditableText); + + let evtPromise = waitForTextChangeEvents(acc, [EVENT_TEXT_REMOVED]); + acc.deleteText( + staticContentOffset + startOffset, + staticContentOffset + endOffset + ); + let evt = (await evtPromise)[0]; + evt.QueryInterface(nsIAccessibleTextChangeEvent); + is(evt.start, staticContentOffset + startOffset); +} + +async function testCopyText( + acc, + startOffset, + endOffset, + staticContentOffset, + browser, + aExpectedClipboard = null +) { + acc.QueryInterface(nsIAccessibleEditableText); + let copied = waitForCopy(browser); + acc.copyText( + staticContentOffset + startOffset, + staticContentOffset + endOffset + ); + let clipboardText = await copied; + if (aExpectedClipboard != null) { + is(clipboardText, aExpectedClipboard, "Correct text in clipboard"); + } +} + +async function testPasteText(acc, insertOffset, staticContentOffset) { + acc.QueryInterface(nsIAccessibleEditableText); + let evtPromise = waitForTextChangeEvents(acc, [EVENT_TEXT_INSERTED]); + acc.pasteText(staticContentOffset + insertOffset); + + let evt = (await evtPromise)[0]; + evt.QueryInterface(nsIAccessibleTextChangeEvent); + // XXX: In non-headless mode pasting text produces several text leaves + // and the offset is not what we expect. + // is(evt.start, staticContentOffset + insertOffset); +} + +async function testCutText(acc, startOffset, endOffset, staticContentOffset) { + acc.QueryInterface(nsIAccessibleEditableText); + let evtPromise = waitForTextChangeEvents(acc, [EVENT_TEXT_REMOVED]); + acc.cutText( + staticContentOffset + startOffset, + staticContentOffset + endOffset + ); + + let evt = (await evtPromise)[0]; + evt.QueryInterface(nsIAccessibleTextChangeEvent); + is(evt.start, staticContentOffset + startOffset); +} |