blob: 8a2b919213cc84a7ea70c5082228423b8a734544 [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']);
/**
* @constructor
* @extends {SwitchAccessE2ETest}
*/
function SwitchAccessTextNavigationManagerTest() {
SwitchAccessE2ETest.call(this);
}
SwitchAccessTextNavigationManagerTest.prototype =
{
__proto__: SwitchAccessE2ETest.prototype,
/** @override */
setUp: function() {
this.textNavigationManager =
switchAccess.navigationManager_.menuManager_.textNavigationManager_;
this.navigationManager =
switchAccess.navigationManager_;
}
}
/**
* 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(desktop) {
const inputNode = findNodeById(desktop, 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(desktop) {
const inputNode = findNodeById(desktop, textId, testHelper);
assertNotEquals(inputNode, null);
checkNodeIsFocused(inputNode);
let 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;
}
/**
* Given the desktop node, returns the node with the given
* id.
* @param {!chrome.automation.AutomationNode} desktop
* @param {string} id
* @return {!chrome.automation.AutomationNode}
*/
function findNodeById(desktop, id) {
// The loop ensures that the page has loaded before trying to find the node.
let inputNode;
while (inputNode == null) {
inputNode = new AutomationTreeWalker(
desktop, constants.Dir.FORWARD,
{visit: (node) => node.htmlAttributes.id === id})
.next()
.node;
}
return inputNode;
}
/**
* 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 {!chrome.automation.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 {!chrome.automation.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: () => {
this.textNavigationManager.jumpToBeginning()
}
});
});
TEST_F('SwitchAccessTextNavigationManagerTest', 'JumpToEnd', function() {
runTextNavigationTest(this, {
content: 'hi there',
initialIndex: 3,
targetIndex: 8,
navigationAction: () => {
this.textNavigationManager.jumpToEnd()
}
});
});
TEST_F(
'SwitchAccessTextNavigationManagerTest', 'MoveBackwardOneChar', function() {
runTextNavigationTest(this, {
content: 'parrots!',
initialIndex: 7,
targetIndex: 6,
navigationAction: () => {
this.textNavigationManager.moveBackwardOneChar()
}
});
});
TEST_F(
'SwitchAccessTextNavigationManagerTest', 'MoveBackwardOneWord', function() {
runTextNavigationTest(this, {
content: 'more parrots!',
initialIndex: 5,
targetIndex: 0,
navigationAction: () => {
this.textNavigationManager.moveBackwardOneWord()
}
});
});
TEST_F(
'SwitchAccessTextNavigationManagerTest', 'MoveForwardOneChar', function() {
runTextNavigationTest(this, {
content: 'hello',
initialIndex: 0,
targetIndex: 1,
navigationAction: () => {
this.textNavigationManager.moveForwardOneChar()
}
});
});
TEST_F(
'SwitchAccessTextNavigationManagerTest', 'MoveForwardOneWord', function() {
runTextNavigationTest(this, {
content: 'more parrots!',
initialIndex: 4,
targetIndex: 12,
navigationAction: () => {
this.textNavigationManager.moveForwardOneWord()
}
});
});
TEST_F('SwitchAccessTextNavigationManagerTest', 'MoveUpOneLine', function() {
runTextNavigationTest(this, {
content: 'more parrots!',
initialIndex: 7,
targetIndex: 2,
cols: 8,
wrap: 'hard',
navigationAction: () => {
this.textNavigationManager.moveUpOneLine();
}
});
});
TEST_F('SwitchAccessTextNavigationManagerTest', 'MoveDownOneLine', function() {
runTextNavigationTest(this, {
content: 'more parrots!',
initialIndex: 3,
targetIndex: 8,
cols: 8,
wrap: 'hard',
navigationAction: () => {
this.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(desktop) {
const inputNode = findNodeById(desktop, 'test', this);
assertNotEquals(inputNode, null);
checkNodeIsFocused(inputNode);
this.textNavigationManager.saveSelectStart();
let startIndex = this.textNavigationManager.getSelStartIndex();
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(desktop) {
const inputNode = findNodeById(desktop, 'test', this);
assertNotEquals(inputNode, null);
checkNodeIsFocused(inputNode)
let startIndex = 3;
this.textNavigationManager.setSelStartIndexAndNode(startIndex, inputNode);
this.textNavigationManager.saveSelectEnd();
let 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: () => {
this.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: () => {
this.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;