blob: a08d4500b956284c67e8afd2aa4bd758fbf18728 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
GEN_INCLUDE(['switch_access_e2e_test_base.js']);
/** Text fixture for the text navigation manager. */
SwitchAccessTextNavigationManagerTest = class extends SwitchAccessE2ETest {
/** @override */
setUp() {
TextNavigationManager.initialize();
this.textNavigationManager = TextNavigationManager.instance;
this.navigationManager = NavigationManager.instance;
}
};
/**
* Generates a website with a text area, finds the node for the text
* area, sets up the node to listen for a text navigation action, and then
* executes the specified text navigation action. Upon detecting the
* text navigation action, the node will verify that the action correctly
* changed the index of the text caret.
* @param {!SwitchAccessE2ETest} testHelper
* @param {{content: string,
* initialIndex: number,
* targetIndex: number,
* navigationAction: function(),
* id: (string || undefined),
* cols: (number || undefined),
* wrap: (string || undefined)}} textParams
*/
function runTextNavigationTest(testHelper, textParams) {
// Required parameters.
const textContent = textParams.content;
const initialTextIndex = textParams.initialIndex;
const targetTextIndex = textParams.targetIndex;
const textNavigationAction = textParams.navigationAction;
// Default parameters.
const textId = textParams.id || 'test';
const textCols = textParams.cols || 20;
const textWrap = textParams.wrap || 'soft';
const website = generateWebsiteWithTextArea(
textId, textContent, initialTextIndex, textCols, textWrap);
testHelper.runWithLoadedTree(website, function(root) {
const inputNode = this.findNodeById(textId);
assertNotEquals(inputNode, null);
setUpCursorChangeListener(
testHelper, inputNode, initialTextIndex, targetTextIndex,
targetTextIndex);
textNavigationAction();
});
}
/**
* This function:
* - Generates a website with a text area
* - Executes setSelectStart finds the node for the text
* area
* - Sets up the node to listen for a text navigation action
* - executes the specified text navigation action. Upon detecting the
* - Verifies that the action correctly changed the index of the text caret
* - Sets up a second listener for a text selection action
* - Calls saveSelectEnd function from the event listener
* - Verifies that the selection was set correctly
* textParams should specify parameters
* for the test as follows:
* -content: content of the text area.
* -initialIndex: index of the text caret before the navigation action.
* -targetStartIndex: start index of the selection after the selection action.
* -targetEndIndex: end index of the selection after the navigation action.
* -navigationAction: function executing a text navigation action or selection
* action. -id: id of the text area element (optional). -cols: number of columns
* in the text area (optional). -wrap: the wrap attribute ("hard" or "soft") of
* the text area (optional).
*
* @param {!SwitchAccessE2ETest} testHelper
* @param {selectionTextParams} textParams,
*/
function runTextSelectionTest(testHelper, textParams) {
// Required parameters.
const textContent = textParams.content;
const initialTextIndex = textParams.initialIndex;
const targetTextStartIndex = textParams.targetStartIndex;
const targetTextEndIndex = textParams.targetEndIndex;
const textNavigationAction = textParams.navigationAction;
// Default parameters.
const selectionIsBackward = textParams.backward || false;
const textId = textParams.id || 'test';
const textCols = textParams.cols || 20;
const textWrap = textParams.wrap || 'soft';
const website = generateWebsiteWithTextArea(
textId, textContent, initialTextIndex, textCols, textWrap);
let navigationTargetIndex = targetTextEndIndex;
if (selectionIsBackward) {
navigationTargetIndex = targetTextStartIndex;
}
testHelper.runWithLoadedTree(website, function(root) {
const inputNode = this.findNodeById(textId);
assertNotEquals(inputNode, null);
checkNodeIsFocused(inputNode);
const callback = testHelper.newCallback(function() {
setUpCursorChangeListener(
testHelper, inputNode, targetTextEndIndex, targetTextStartIndex,
targetTextEndIndex);
testHelper.textNavigationManager.saveSelectEnd();
});
testHelper.textNavigationManager.saveSelectStart();
setUpCursorChangeListener(
testHelper, inputNode, initialTextIndex, navigationTargetIndex,
navigationTargetIndex, callback);
textNavigationAction();
});
}
/**
* Returns a website string with a text area with the given properties.
* @param {number} id The ID of the text area element.
* @param {string} contents The contents of the text area.
* @param {number} textIndex The index of the text caret within the text area.
* @param {number} cols The number of columns in the text area.
* @param {string} wrap The wrap attribute of the text area ('hard' or 'soft').
* @return {string}
*/
function generateWebsiteWithTextArea(id, contents, textIndex, cols, wrap) {
const website = `data:text/html;charset=utf-8,
<textarea id=${id} cols=${cols} wrap=${wrap}
autofocus="true">${contents}</textarea>
<script>
const input = document.getElementById("${id}");
input.selectionStart = ${textIndex};
input.selectionEnd = ${textIndex};
input.focus();
</script>`;
return website;
}
/**
* Check that the node in the JS file matches the node in the test.
* The nodes can be assumed to be the same if their roles match as there is only
* one text input node on the generated webpage.
* @param {!AutomationNode} inputNode
*/
function checkNodeIsFocused(inputNode) {
chrome.automation.getFocus((focusedNode) => {
assertEquals(focusedNode.role, inputNode.role);
});
}
/**
* Sets up the input node (text field) to listen for text
* navigation and selection actions. When the index of the text selection
* changes from its initial position, checks that the text
* indices now matches the target text start and end index. Assumes that the
* initial and target text start/end indices are distinct (to detect a
* change from the text navigation action). Also assumes that
* the text navigation and selection actions directly changes the text caret
* to the correct index (with no intermediate movements).
* @param {!SwitchAccessE2ETest} testHelper
* @param {!AutomationNode} inputNode
* @param {number} initialTextIndex
* @param {number} targetTextStartIndex
* @param {number} targetTextEndIndex
* @param {function() || undefined} callback
*/
function setUpCursorChangeListener(
testHelper, inputNode, initialTextIndex, targetTextStartIndex,
targetTextEndIndex, callback) {
// Ensures that the text index has changed before checking the new index.
const checkActionFinished = function(tab) {
if (inputNode.textSelStart !== initialTextIndex ||
inputNode.textSelEnd !== initialTextIndex) {
checkTextIndex();
if (callback) {
callback();
}
}
};
// Test will not exit until this check is called.
const checkTextIndex = testHelper.newCallback(function() {
assertEquals(inputNode.textSelStart, targetTextStartIndex);
assertEquals(inputNode.textSelEnd, targetTextEndIndex);
// If there's a callback then this is the navigation listener for a
// selection test, thus remove it when fired to make way for the selection
// listener.
if (callback) {
inputNode.removeEventListener(
chrome.automation.EventType.TEXT_SELECTION_CHANGED,
checkActionFinished);
}
});
inputNode.addEventListener(
chrome.automation.EventType.TEXT_SELECTION_CHANGED, checkActionFinished);
}
TEST_F('SwitchAccessTextNavigationManagerTest', 'JumpToBeginning', function() {
runTextNavigationTest(this, {
content: 'hi there',
initialIndex: 6,
targetIndex: 0,
navigationAction: () => {
TextNavigationManager.jumpToBeginning();
}
});
});
TEST_F('SwitchAccessTextNavigationManagerTest', 'JumpToEnd', function() {
runTextNavigationTest(this, {
content: 'hi there',
initialIndex: 3,
targetIndex: 8,
navigationAction: () => {
TextNavigationManager.jumpToEnd();
}
});
});
TEST_F(
'SwitchAccessTextNavigationManagerTest', 'MoveBackwardOneChar', function() {
runTextNavigationTest(this, {
content: 'parrots!',
initialIndex: 7,
targetIndex: 6,
navigationAction: () => {
TextNavigationManager.moveBackwardOneChar();
}
});
});
TEST_F(
'SwitchAccessTextNavigationManagerTest', 'MoveBackwardOneWord', function() {
runTextNavigationTest(this, {
content: 'more parrots!',
initialIndex: 5,
targetIndex: 0,
navigationAction: () => {
TextNavigationManager.moveBackwardOneWord();
}
});
});
TEST_F(
'SwitchAccessTextNavigationManagerTest', 'MoveForwardOneChar', function() {
runTextNavigationTest(this, {
content: 'hello',
initialIndex: 0,
targetIndex: 1,
navigationAction: () => {
TextNavigationManager.moveForwardOneChar();
}
});
});
TEST_F(
'SwitchAccessTextNavigationManagerTest', 'MoveForwardOneWord', function() {
runTextNavigationTest(this, {
content: 'more parrots!',
initialIndex: 4,
targetIndex: 12,
navigationAction: () => {
TextNavigationManager.moveForwardOneWord();
}
});
});
TEST_F('SwitchAccessTextNavigationManagerTest', 'MoveUpOneLine', function() {
runTextNavigationTest(this, {
content: 'more parrots!',
initialIndex: 7,
targetIndex: 2,
cols: 8,
wrap: 'hard',
navigationAction: () => {
TextNavigationManager.moveUpOneLine();
}
});
});
TEST_F('SwitchAccessTextNavigationManagerTest', 'MoveDownOneLine', function() {
runTextNavigationTest(this, {
content: 'more parrots!',
initialIndex: 3,
targetIndex: 8,
cols: 8,
wrap: 'hard',
navigationAction: () => {
TextNavigationManager.moveDownOneLine();
}
});
});
/**
* Test the setSelectStart function by checking correct index is stored as the
* selection start index.
*/
TEST_F(
'SwitchAccessTextNavigationManagerTest', 'DISABLED_SelectStart',
function() {
const website =
generateWebsiteWithTextArea('test', 'test123', 3, 20, 'hard');
this.runWithLoadedTree(website, function(root) {
const inputNode = this.findNodeById('test');
assertNotEquals(inputNode, null);
checkNodeIsFocused(inputNode);
this.textNavigationManager.saveSelectStart();
const startIndex = this.textNavigationManager.selectionStartIndex_;
assertEquals(startIndex, 3);
});
});
/**
* Test the setSelectEnd function by manually setting the selection start index
* and node then calling setSelectEnd and checking for the correct selection
* bounds
*/
TEST_F(
'SwitchAccessTextNavigationManagerTest', 'DISABLED_SelectEnd', function() {
const website =
generateWebsiteWithTextArea('test', 'test 123', 6, 20, 'hard');
this.runWithLoadedTree(website, function(root) {
const inputNode = this.findNodeById('test');
assertNotEquals(inputNode, null);
checkNodeIsFocused(inputNode);
const startIndex = 3;
this.textNavigationManager.selectionStartIndex_ = startIndex;
this.textNavigationManager.selectionStartObject_ = inputNode;
this.textNavigationManager.saveSelectEnd();
const endIndex = inputNode.textSelEnd;
assertEquals(6, endIndex);
});
});
/**
* Test use of setSelectStart and setSelectEnd with the moveForwardOneChar
* function.
*/
TEST_F(
'SwitchAccessTextNavigationManagerTest', 'DISABLED_SelectCharacter',
function() {
runTextSelectionTest(this, {
content: 'hello world!',
initialIndex: 0,
targetStartIndex: 0,
targetEndIndex: 1,
cols: 8,
wrap: 'hard',
navigationAction: () => {
TextNavigationManager.moveForwardOneChar();
}
});
});
/**
* Test use of setSelectStart and setSelectEnd with a backward selection using
* the moveBackwardOneWord function.
*/
TEST_F(
'SwitchAccessTextNavigationManagerTest', 'DISABLED_SelectWordBackward',
function() {
runTextSelectionTest(this, {
content: 'hello world!',
initialIndex: 5,
targetStartIndex: 0,
targetEndIndex: 5,
cols: 8,
wrap: 'hard',
navigationAction: () => {
TextNavigationManager.moveBackwardOneWord();
},
backward: true
});
});
/**
* selectionTextParams should specify parameters
* for the test as follows:
* -content: content of the text area.
* -initialIndex: index of the text caret before the navigation action.
* -targetIndex: index of the text caret after the navigation action.
* -navigationAction: function executing a text navigation action.
* -id: id of the text area element (optional).
* -cols: number of columns in the text area (optional).
* -wrap: the wrap attribute ("hard" or "soft") of the text area (optional).
*@typedef {{content: string,
* initialIndex: number,
* targetStartIndex: number,
* targetEndIndex: number,
* textAction: function(),
* backward: (boolean || undefined)
* id: (string || undefined),
* cols: (number || undefined),
* wrap: (string || undefined),}}
*/
let selectionTextParams;