Commit bf0a104c authored by Zach Helfinstein's avatar Zach Helfinstein Committed by Commit Bot

Refactor SwitchAccess to use same TreeWalker as CVox

Bug: 873016
Change-Id: If8ee78b9e739aaad0403964948534c3305b26580
Reviewed-on: https://chromium-review.googlesource.com/1170383Reviewed-by: default avatarDavid Tseng <dtseng@chromium.org>
Commit-Queue: Zach Helfinstein <zhelfins@chromium.org>
Cr-Commit-Position: refs/heads/master@{#582169}
parent f3b7eb37
......@@ -7,6 +7,7 @@ import("//chrome/common/features.gni")
import("//components/nacl/features.gni")
import("//testing/test.gni")
import("//chrome/test/base/js2gtest.gni")
import("//third_party/closure_compiler/compile_js.gni")
import("run_jsbundler.gni")
assert(is_chromeos)
......@@ -643,3 +644,36 @@ js2gtest("chromevox_extjs_tests") {
]
defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
}
js_library("tree_walker") {
sources = [
"cvox2/background/tree_walker.js",
]
deps = [
":automation_predicate",
":constants",
]
externs_list = [
"$externs_path/automation.js",
"$externs_path/chrome_extensions.js",
]
}
js_library("automation_predicate") {
sources = [
"cvox2/background/automation_predicate.js",
]
deps = [
":constants",
]
externs_list = [
"$externs_path/automation.js",
"$externs_path/chrome_extensions.js",
]
}
js_library("constants") {
sources = [
"cvox2/background/constants.js",
]
}
......@@ -26,9 +26,11 @@ run_jsbundler("switch_access_copied_files") {
mode = "copy"
dest_dir = switch_access_dir
sources = [
"../chromevox/cvox2/background/constants.js",
"../chromevox/cvox2/background/tree_walker.js",
"../select_to_speak/closure_shim.js",
"auto_scan_manager.js",
"automation_manager.js",
"automation_predicate.js",
"background.js",
"commands.js",
"keyboard_handler.js",
......@@ -37,11 +39,13 @@ run_jsbundler("switch_access_copied_files") {
"options.js",
"prefs.js",
"switch_access.js",
"tree_walker.js",
"switch_access_predicate.js",
]
rewrite_rules = [
rebase_path(".", root_build_dir) + ":",
rebase_path(closure_library_dir, root_build_dir) + ":closure",
rebase_path("../chromevox/cvox2/background", root_build_dir) + ":",
rebase_path("../select_to_speak", root_build_dir) + ":",
]
}
......@@ -56,14 +60,12 @@ js2gtest("switch_access_webuijs_tests") {
test_type = "webui"
sources = [
"auto_scan_manager_unittest.gtestjs",
"automation_predicate_unittest.gtestjs",
"tree_walker_unittest.gtestjs",
"switch_access_predicate_unittest.gtestjs",
]
extra_js_files = [
"auto_scan_manager.js",
"automation_predicate.js",
"switch_access_predicate.js",
"test_support.js",
"tree_walker.js",
]
defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
}
......@@ -113,16 +115,18 @@ js_type_check("closure_compile") {
deps = [
":auto_scan_manager",
":automation_manager",
":automation_predicate",
":background",
":commands",
":keyboard_handler",
":options",
":prefs",
":switch_access_predicate",
"../chromevox:constants",
"../chromevox:tree_walker",
"../select_to_speak:closure_shim",
# ":switch_access",
":switch_access_interface",
":tree_walker",
]
}
......@@ -134,8 +138,9 @@ js_library("auto_scan_manager") {
js_library("automation_manager") {
deps = [
":automation_predicate",
":tree_walker",
":switch_access_predicate",
"../chromevox:constants",
"../chromevox:tree_walker",
]
externs_list = [
"$externs_path/accessibility_private.js",
......@@ -143,7 +148,7 @@ js_library("automation_manager") {
]
}
js_library("automation_predicate") {
js_library("switch_access_predicate") {
externs_list = [ "$externs_path/automation.js" ]
}
......@@ -198,7 +203,3 @@ js_library("switch_access") {
js_library("switch_access_interface") {
}
js_library("tree_walker") {
externs_list = [ "$externs_path/automation.js" ]
}
......@@ -40,13 +40,6 @@ function AutomationManager(desktop) {
*/
this.scopeStack_ = [];
/**
* Moves to the appropriate node in the accessibility tree.
*
* @private {!AutomationTreeWalker}
*/
this.treeWalker_ = this.createTreeWalker_(desktop);
this.init_();
}
......@@ -102,10 +95,9 @@ AutomationManager.prototype = {
// Move to focused node.
this.node_ = event.target;
this.treeWalker_ = this.createTreeWalker_(this.scope_, this.node_);
// In case the node that gained focus is not a subtreeLeaf.
if (AutomationPredicate.isSubtreeLeaf(this.node_, this.scope_)) {
if (SwitchAccessPredicate.isSubtreeLeaf(this.node_, this.scope_)) {
this.printNode_(this.node_);
this.updateFocusRing_();
} else
......@@ -135,7 +127,7 @@ AutomationManager.prototype = {
let ancestor = ancestorList.pop();
if (ancestor.role === chrome.automation.RoleType.DESKTOP)
continue;
if (AutomationPredicate.isGroup(ancestor, this.scope_)) {
if (SwitchAccessPredicate.isGroup(ancestor, this.scope_)) {
this.scopeStack_.push(this.scope_);
this.scope_ = ancestor;
}
......@@ -189,12 +181,24 @@ AutomationManager.prototype = {
// If node is invalid, set node to last valid scope.
this.startAtValidNode_();
let node = this.treeWalker_.moveToNode(doNext);
if (node) {
this.node_ = node;
this.printNode_(this.node_);
this.updateFocusRing_();
let treeWalker = new AutomationTreeWalker(
this.node_, doNext ? constants.Dir.FORWARD : constants.Dir.BACKWARD,
SwitchAccessPredicate.restrictions(this.scope_));
let node = treeWalker.next().node;
// If treeWalker returns undefined, that means we're at the end of the tree
// and we should start over.
if (!node) {
if (doNext)
node = this.scope_;
else
node = this.youngestDescendant_(this.scope_);
}
this.node_ = node;
this.printNode_(this.node_);
this.updateFocusRing_();
},
/**
......@@ -220,17 +224,15 @@ AutomationManager.prototype = {
this.scope_ = this.scopeStack_.pop();
} while (!this.scope_.role && this.scopeStack_.length > 0);
this.treeWalker_ = this.createTreeWalker_(this.scope_, this.node_);
this.updateFocusRing_();
console.log('Moved to previous scope');
this.printNode_(this.node_);
return;
}
if (AutomationPredicate.isGroup(this.node_, this.scope_)) {
if (SwitchAccessPredicate.isGroup(this.node_, this.scope_)) {
this.scopeStack_.push(this.scope_);
this.scope_ = this.node_;
this.treeWalker_ = this.createTreeWalker_(this.scope_);
console.log('Entered scope');
this.moveToNode(true);
return;
......@@ -250,7 +252,7 @@ AutomationManager.prototype = {
let color;
if (this.node_ === this.scope_)
color = AutomationManager.Color.SCOPE;
else if (AutomationPredicate.isGroup(this.node_, this.scope_))
else if (SwitchAccessPredicate.isGroup(this.node_, this.scope_))
color = AutomationManager.Color.GROUP;
else
color = AutomationManager.Color.LEAF;
......@@ -281,36 +283,6 @@ AutomationManager.prototype = {
this.node_ = this.scopeStack_.pop();
this.scope_ = this.node_;
}
this.treeWalker_ = this.createTreeWalker_(this.scope_);
},
/**
* Create an AutomationTreeWalker for the subtree with |scope| as its root.
* If |opt_start| is defined, the tree walker will start walking the tree
* from |opt_start|; otherwise, it will start from |scope|.
*
* @param {!chrome.automation.AutomationNode} scope
* @param {!chrome.automation.AutomationNode=} opt_start
* @private
* @return {!AutomationTreeWalker}
*/
createTreeWalker_: function(scope, opt_start) {
// If no explicit start node, start walking the tree from |scope|.
let start = opt_start || scope;
let leafPred = function(node) {
return (node !== scope &&
AutomationPredicate.isSubtreeLeaf(node, scope)) ||
!AutomationPredicate.isInterestingSubtree(node);
};
let visitPred = function(node) {
// Avoid visiting the top-level root node (i.e., the desktop node).
return node !== this.desktop_ &&
AutomationPredicate.isSubtreeLeaf(node, scope);
}.bind(this);
let restrictions = {leaf: leafPred, visit: visitPred};
return new AutomationTreeWalker(start, scope, restrictions);
},
// TODO(elichtenberg): Move print functions to a custom logger class. Only
......@@ -343,50 +315,19 @@ AutomationManager.prototype = {
},
/**
* Move to the next sibling of this.node_ if it has one.
*/
debugMoveToNext: function() {
let next = this.treeWalker_.debugMoveToNext(this.node_);
if (next) {
this.node_ = next;
this.printNode_(this.node_);
chrome.accessibilityPrivate.setFocusRing([this.node_.location]);
}
},
/**
* Move to the previous sibling of this.node_ if it has one.
* Get the youngest descendant of |node|, if it has one within the current
* scope.
*
* @param {!chrome.automation.AutomationNode} node
* @return {!chrome.automation.AutomationNode}
* @private
*/
debugMoveToPrevious: function() {
let prev = this.treeWalker_.debugMoveToPrevious(this.node_);
if (prev) {
this.node_ = prev;
this.printNode_(this.node_);
chrome.accessibilityPrivate.setFocusRing([this.node_.location]);
}
},
youngestDescendant_: function(node) {
let leaf = SwitchAccessPredicate.leaf(this.scope_);
/**
* Move to the first child of this.node_ if it has one.
*/
debugMoveToFirstChild: function() {
let child = this.treeWalker_.debugMoveToFirstChild(this.node_);
if (child) {
this.node_ = child;
this.printNode_(this.node_);
chrome.accessibilityPrivate.setFocusRing([this.node_.location]);
}
},
while (node.lastChild && !leaf(node))
node = node.lastChild;
/**
* Move to the parent of this.node_ if it has one.
*/
debugMoveToParent: function() {
let parent = this.treeWalker_.debugMoveToParent(this.node_);
if (parent) {
this.node_ = parent;
this.printNode_(this.node_);
chrome.accessibilityPrivate.setFocusRing([this.node_.location]);
}
return node;
}
};
......@@ -78,24 +78,6 @@ Commands.prototype = {
'options': {
'defaultKeyCode': 52, /* '4' key */
'binding': this.switchAccess_.showOptionsPage.bind(this.switchAccess_)
},
'debugNext': {
'defaultKeyCode': -1, /* unused key */
'binding': this.switchAccess_.debugMoveToNext.bind(this.switchAccess_)
},
'debugPrevious': {
'defaultKeyCode': -1, /* unused key */
'binding':
this.switchAccess_.debugMoveToPrevious.bind(this.switchAccess_)
},
'debugChild': {
'defaultKeyCode': -1, /* unused key */
'binding':
this.switchAccess_.debugMoveToFirstChild.bind(this.switchAccess_)
},
'debugParent': {
'defaultKeyCode': -1, /* unused key */
'binding': this.switchAccess_.debugMoveToParent.bind(this.switchAccess_)
}
};
}
......
......@@ -13,8 +13,10 @@
"scripts": [
"auto_scan_manager.js",
"automation_manager.js",
"automation_predicate.js",
"switch_access_predicate.js",
"closure_shim.js",
"commands.js",
"constants.js",
"keyboard_handler.js",
"prefs.js",
"switch_access.js",
......
......@@ -224,45 +224,5 @@ SwitchAccess.prototype = {
*/
keyCodeIsUsed: function(keyCode) {
return this.switchAccessPrefs_.keyCodeIsUsed(keyCode);
},
/**
* Move to the next sibling of the current node if it has one.
*
* @override
*/
debugMoveToNext: function() {
if (this.automationManager_)
this.automationManager_.debugMoveToNext();
},
/**
* Move to the previous sibling of the current node if it has one.
*
* @override
*/
debugMoveToPrevious: function() {
if (this.automationManager_)
this.automationManager_.debugMoveToPrevious();
},
/**
* Move to the first child of the current node if it has one.
*
* @override
*/
debugMoveToFirstChild: function() {
if (this.automationManager_)
this.automationManager_.debugMoveToFirstChild();
},
/**
* Move to the parent of the current node if it has one.
*
* @override
*/
debugMoveToParent: function() {
if (this.automationManager_)
this.automationManager_.debugMoveToParent();
}
};
......@@ -99,25 +99,5 @@ SwitchAccessInterface.prototype = {
* @param {number} keyCode
* @return {boolean}
*/
keyCodeIsUsed: function(keyCode) {},
/**
* Move to the next sibling of the current node if it has one.
*/
debugMoveToNext: function() {},
/**
* Move to the previous sibling of the current node if it has one.
*/
debugMoveToPrevious: function() {},
/**
* Move to the first child of the current node if it has one.
*/
debugMoveToFirstChild: function() {},
/**
* Move to the parent of the current node if it has one.
*/
debugMoveToParent: function() {}
keyCodeIsUsed: function(keyCode) {}
};
......@@ -8,19 +8,75 @@
*
* @constructor
*/
function AutomationPredicate() {}
function SwitchAccessPredicate() {}
/**
* Returns true if |node| is a subtreeLeaf, meaning that |node| is either
* interesting or a group (both defined below).
* Returns a Restrictions object ready to be passed to AutomationTreeWalker.
*
* @param {!chrome.automation.AutomationNode} scope
* @return {!AutomationTreeWalkerRestriction}
*/
SwitchAccessPredicate.restrictions = function(scope) {
return {
leaf: SwitchAccessPredicate.leaf(scope),
root: SwitchAccessPredicate.root(scope),
visit: SwitchAccessPredicate.visit(scope)
};
};
/**
* Creates a function that confirms if |node| is a terminal leaf node of a
* SwitchAccess scope tree when |scope| is the root.
*
* @param {!chrome.automation.AutomationNode} scope
* @return {function(!chrome.automation.AutomationNode): boolean}
*/
SwitchAccessPredicate.leaf = function(scope) {
return function(node) {
return (node !== scope &&
SwitchAccessPredicate.isSubtreeLeaf(node, scope)) ||
!SwitchAccessPredicate.isInterestingSubtree(node);
}.bind(scope);
};
/**
* Creates a function that confirms if |node| is the root of a SwitchAccess
* scope tree when |scope| is the root.
*
* @param {!chrome.automation.AutomationNode} scope
* @return {function(!chrome.automation.AutomationNode): boolean}
*/
SwitchAccessPredicate.root = function(scope) {
return function(node) {
return node === scope;
}.bind(scope);
};
/**
* Creates a function that determines whether |node| is to be visited in the
* SwitchAccess scope tree with |scope| as the root.
*
* @param {!chrome.automation.AutomationNode} scope
* @return {function(!chrome.automation.AutomationNode): boolean}
*/
SwitchAccessPredicate.visit = function(scope) {
return function(node) {
return node.role !== chrome.automation.RoleType.DESKTOP &&
SwitchAccessPredicate.isSubtreeLeaf(node, scope);
}.bind(scope);
};
/**
* Returns true if |node| is a subtreeLeaf, meaning that |node|
* is either interesting or a group (both defined below).
*
* @param {!chrome.automation.AutomationNode} node
* @param {!chrome.automation.AutomationNode} scope
* @return {boolean}
*/
AutomationPredicate.isSubtreeLeaf = function(node, scope) {
return AutomationPredicate.isActionable(node) ||
AutomationPredicate.isGroup(node, scope);
SwitchAccessPredicate.isSubtreeLeaf = function(node, scope) {
return SwitchAccessPredicate.isActionable(node) ||
SwitchAccessPredicate.isGroup(node, scope);
};
/**
......@@ -35,8 +91,8 @@ AutomationPredicate.isSubtreeLeaf = function(node, scope) {
* @param {!chrome.automation.AutomationNode} scope
* @return {boolean}
*/
AutomationPredicate.isGroup = function(node, scope) {
if (node !== scope && AutomationPredicate.hasSameLocation_(node, scope))
SwitchAccessPredicate.isGroup = function(node, scope) {
if (node !== scope && SwitchAccessPredicate.hasSameLocation_(node, scope))
return false;
// Work around for client nested in client. No need to have user select both
......@@ -49,7 +105,7 @@ AutomationPredicate.isGroup = function(node, scope) {
let interestingBranches = 0;
let children = node.children || [];
for (let child of children) {
if (AutomationPredicate.isInterestingSubtree(child))
if (SwitchAccessPredicate.isInterestingSubtree(child))
interestingBranches += 1;
if (interestingBranches > 1)
return true;
......@@ -64,7 +120,7 @@ AutomationPredicate.isGroup = function(node, scope) {
* @param {!chrome.automation.AutomationNode} node2
* @return {boolean}
*/
AutomationPredicate.hasSameLocation_ = function(node1, node2) {
SwitchAccessPredicate.hasSameLocation_ = function(node1, node2) {
let l1 = node1.location;
let l2 = node2.location;
return l1.left === l2.left && l1.top === l2.top && l1.width === l2.width &&
......@@ -78,10 +134,10 @@ AutomationPredicate.hasSameLocation_ = function(node1, node2) {
* @param {!chrome.automation.AutomationNode} node
* @return {boolean}
*/
AutomationPredicate.isInterestingSubtree = function(node) {
SwitchAccessPredicate.isInterestingSubtree = function(node) {
let children = node.children || [];
return AutomationPredicate.isActionable(node) ||
children.some(AutomationPredicate.isInterestingSubtree);
return SwitchAccessPredicate.isActionable(node) ||
children.some(SwitchAccessPredicate.isInterestingSubtree);
};
/**
......@@ -91,7 +147,7 @@ AutomationPredicate.isInterestingSubtree = function(node) {
* @param {!chrome.automation.AutomationNode} node
* @return {boolean}
*/
AutomationPredicate.isActionable = function(node) {
SwitchAccessPredicate.isActionable = function(node) {
let loc = node.location;
let parent = node.parent;
let root = node.root;
......
// Copyright 2017 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.
/**
* Class to move to the appropriate node in the accessibility tree. Stays in a
* subtree determined by restrictions passed to it.
*
* @constructor
* @param {!chrome.automation.AutomationNode} start
* @param {!chrome.automation.AutomationNode} scope
* @param {!AutomationTreeWalker.Restriction} restrictions
*/
function AutomationTreeWalker(start, scope, restrictions) {
/**
* Currently highlighted node.
*
* @private {!chrome.automation.AutomationNode}
*/
this.node_ = start;
/**
* The root of the subtree that the user is navigating through.
*
* @private {!chrome.automation.AutomationNode}
*/
this.scope_ = scope;
/**
* Function that returns true for a node that is a leaf of the current
* subtree.
*
* @private {!AutomationTreeWalker.Unary}
*/
this.leafPred_ = restrictions.leaf;
/**
* Function that returns true for a node in the current subtree that should
* be visited.
*
* @private {!AutomationTreeWalker.Unary}
*/
this.visitPred_ = restrictions.visit;
}
/**
* @typedef {{leaf: AutomationTreeWalker.Unary,
* visit: AutomationTreeWalker.Unary}}
*/
AutomationTreeWalker.Restriction;
/**
* @typedef {function(!chrome.automation.AutomationNode) : boolean}
*/
AutomationTreeWalker.Unary;
AutomationTreeWalker.prototype = {
/**
* Set this.node_ to the next/previous interesting node within the current
* scope and return it. If no interesting node is found, return the
* first/last interesting node. If |doNext| is true, will search for next
* node. Otherwise, will search for previous node.
*
* @param {boolean} doNext
* @return {chrome.automation.AutomationNode}
*/
moveToNode: function(doNext) {
let node = this.node_;
do {
node = doNext ? this.getNextNode_(node) : this.getPreviousNode_(node);
} while (node && !this.visitPred_(node));
if (node) {
this.node_ = node;
return node;
}
console.log('Restarting search for node at ' + (doNext ? 'first' : 'last'));
node = doNext ? this.scope_ : this.getYoungestDescendant_(this.scope_);
while (node && !this.visitPred_(node))
node = doNext ? this.getNextNode_(node) : this.getPreviousNode_(node);
if (node) {
this.node_ = node;
return node;
}
console.log('Found no interesting nodes to visit.');
return null;
},
/**
* Given a flat list of nodes in pre-order, get the node that comes after
* |node| within the current scope.
*
* @param {!chrome.automation.AutomationNode} node
* @return {!chrome.automation.AutomationNode|undefined}
* @private
*/
getNextNode_: function(node) {
// Check for child.
let child = node.firstChild;
if (child && !this.leafPred_(node))
return child;
// Has no children, and if node is root of subtree, don't check siblings
// or parent.
if (node === this.scope_)
return undefined;
// No child. Check for right-sibling.
let sibling = node.nextSibling;
if (sibling)
return sibling;
// No right-sibling. Get right-sibling of closest ancestor.
let ancestor = node.parent;
while (ancestor && ancestor !== this.scope_) {
let aunt = ancestor.nextSibling;
if (aunt)
return aunt;
ancestor = ancestor.parent;
}
// No node found after |node|, so return undefined.
return undefined;
},
/**
* Given a flat list of nodes in pre-order, get the node that comes before
* |node| within the current scope.
*
* @param {!chrome.automation.AutomationNode} node
* @return {!chrome.automation.AutomationNode|undefined}
* @private
*/
getPreviousNode_: function(node) {
// If node is root of subtree, there is no previous node.
if (node === this.scope_)
return undefined;
// Check for left-sibling. If a left-sibling exists, return its youngest
// descendant if it has one, or otherwise return the sibling.
let sibling = node.previousSibling;
if (sibling)
return this.getYoungestDescendant_(sibling) || sibling;
// No left-sibling. Return parent if it exists; otherwise return undefined.
let parent = node.parent;
if (parent)
return parent;
return undefined;
},
/**
* Get the youngest descendant of |node|, if it has one, within the current
* scope.
*
* @param {!chrome.automation.AutomationNode} node
* @return {!chrome.automation.AutomationNode|undefined}
* @private
*/
getYoungestDescendant_: function(node) {
if (!node.lastChild || this.leafPred_(node))
return undefined;
while (node.lastChild && !this.leafPred_(node))
node = node.lastChild;
return node;
},
/**
* Return the next sibling of |node| if it has one.
*
* @param {chrome.automation.AutomationNode} node
* @return {chrome.automation.AutomationNode}
*/
debugMoveToNext: function(node) {
if (!node)
return null;
let next = node.nextSibling;
if (next) {
return next;
} else {
console.log('Node is last of siblings');
console.log('\n');
return null;
}
},
/**
* Return the previous sibling of |node| if it has one.
*
* @param {chrome.automation.AutomationNode} node
* @return {chrome.automation.AutomationNode}
*/
debugMoveToPrevious: function(node) {
if (!node)
return null;
let prev = node.previousSibling;
if (prev) {
return prev;
} else {
console.log('Node is first of siblings');
console.log('\n');
return null;
}
},
/**
* Return the first child of |node| if it has one.
*
* @param {chrome.automation.AutomationNode} node
* @return {chrome.automation.AutomationNode}
*/
debugMoveToFirstChild: function(node) {
if (!node)
return null;
let child = node.firstChild;
if (child) {
return child;
} else {
console.log('Node has no children');
console.log('\n');
return null;
}
},
/**
* Return the parent of |node| if it has one.
*
* @param {chrome.automation.AutomationNode} node
* @return {chrome.automation.AutomationNode}
*/
debugMoveToParent: function(node) {
if (!node)
return null;
let parent = node.parent;
if (parent) {
return parent;
} else {
console.log('Node has no parent');
console.log('\n');
return null;
}
}
};
// Copyright 2017 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.
/**
* Test fixture for tree_walker.js.
* @constructor
* @extends {testing.Test}
*/
function AutomationTreeWalkerUnitTest() {
testing.Test.call(this);
};
AutomationTreeWalkerUnitTest.prototype = {
__proto__: testing.Test.prototype,
/** @override */
extraLibraries: [
'test_support.js',
'tree_walker.js',
],
/** @override */
browsePreload: DUMMY_URL,
getSampleTree: function() {
let loc = {left: 0, top: 0, width: 0, height: 0};
// root
// upper1
// middle1
// lower1
// leaf1
// leaf2
// leaf3
// lower2
// leaf4
// leaf5
// middle2
// lower3
// leaf6
// leaf7
// upper2
// leaf8
let root = {location: loc, state: {}};
let upper1 = {location: loc, state: {}};
let upper2 = {location: loc, state: {}};
let middle1 = {location: loc, state: {}};
let middle2 = {location: loc, state: {}};
let lower1 = {location: loc, state: {}};
let lower2 = {location: loc, state: {}};
let lower3 = {location: loc, state: {}};
let leaf1 = {location: loc, state: {}};
let leaf2 = {location: loc, state: {}};
let leaf3 = {location: loc, state: {}};
let leaf4 = {location: loc, state: {}};
let leaf5 = {location: loc, state: {}};
let leaf6 = {location: loc, state: {}};
let leaf7 = {location: loc, state: {}};
let leaf8 = {location: loc, state: {}};
let ts = new TestSupport();
ts.setChildren(root, [upper1, upper2]);
ts.setChildren(upper1, [middle1, middle2]);
ts.setChildren(upper2, [leaf8]);
ts.setChildren(middle1, [lower1, lower2]);
ts.setChildren(middle2, [lower3]);
ts.setChildren(lower1, [leaf1, leaf2, leaf3]);
ts.setChildren(lower2, [leaf4, leaf5]);
ts.setChildren(lower3, [leaf6, leaf7]);
return {
root: root,
upper1: upper1,
upper2: upper2,
middle1: middle1,
middle2: middle2,
lower1: lower1,
lower2: lower2,
lower3: lower3,
leaf1: leaf1,
leaf2: leaf2,
leaf3: leaf3,
leaf4: leaf4,
leaf5: leaf5,
leaf6: leaf6,
leaf7: leaf7,
leaf8: leaf8
};
},
getDefaultRestrictions: function() {
return {
leaf: function(node) {
return false;
},
visit: function(node) {
return node.state.focusable === true;
},
}
},
getSubtreeRestrictions: function() {
return {
leaf: function(node) {
return node.state.leaf === true;
},
visit: function(node) {
return node.state.focusable === true;
},
}
}
};
TEST_F('AutomationTreeWalkerUnitTest', 'MoveToNodeWholeTree', function() {
let t = this.getSampleTree();
let treeWalker = new AutomationTreeWalker(
t.root, t.root, this.getDefaultRestrictions());
t.root.state['focusable'] = true;
t.middle1.state['focusable'] = true;
t.leaf1.state['focusable'] = true;
t.leaf2.state['focusable'] = true;
t.leaf5.state['focusable'] = true;
t.lower3.state['focusable'] = true;
t.leaf6.state['focusable'] = true;
t.upper2.state['focusable'] = true;
t.leaf8.state['focusable'] = true;
// Move to next node.
assertEquals(t.middle1, treeWalker.moveToNode(true));
assertEquals(t.leaf1, treeWalker.moveToNode(true));
assertEquals(t.leaf2, treeWalker.moveToNode(true));
assertEquals(t.leaf5, treeWalker.moveToNode(true));
assertEquals(t.lower3, treeWalker.moveToNode(true));
assertEquals(t.leaf6, treeWalker.moveToNode(true));
assertEquals(t.upper2, treeWalker.moveToNode(true));
assertEquals(t.leaf8, treeWalker.moveToNode(true));
assertEquals(t.root, treeWalker.moveToNode(true));
assertEquals(t.middle1, treeWalker.moveToNode(true));
// Move to previous node.
assertEquals(t.root, treeWalker.moveToNode(false));
assertEquals(t.leaf8, treeWalker.moveToNode(false));
assertEquals(t.upper2, treeWalker.moveToNode(false));
assertEquals(t.leaf6, treeWalker.moveToNode(false));
assertEquals(t.lower3, treeWalker.moveToNode(false));
assertEquals(t.leaf5, treeWalker.moveToNode(false));
assertEquals(t.leaf2, treeWalker.moveToNode(false));
assertEquals(t.leaf1, treeWalker.moveToNode(false));
assertEquals(t.middle1, treeWalker.moveToNode(false));
});
TEST_F('AutomationTreeWalkerUnitTest', 'MoveToNodeInSubtree', function() {
let t = this.getSampleTree();
let treeWalker = new AutomationTreeWalker(
t.upper1, t.upper1, this.getSubtreeRestrictions());
t.lower2.state['leaf'] = true;
t.lower3.state['leaf'] = true;
t.root.state['focusable'] = true;
t.middle1.state['focusable'] = true;
t.leaf1.state['focusable'] = true;
t.leaf2.state['focusable'] = true;
t.leaf5.state['focusable'] = true;
t.lower3.state['focusable'] = true;
t.leaf6.state['focusable'] = true;
t.upper2.state['focusable'] = true;
t.leaf8.state['focusable'] = true;
// Move to next node.
assertEquals(t.middle1, treeWalker.moveToNode(true));
assertEquals(t.leaf1, treeWalker.moveToNode(true));
assertEquals(t.leaf2, treeWalker.moveToNode(true));
assertEquals(t.lower3, treeWalker.moveToNode(true));
assertEquals(t.middle1, treeWalker.moveToNode(true));
// Move to previous node.
assertEquals(t.lower3, treeWalker.moveToNode(false));
assertEquals(t.leaf2, treeWalker.moveToNode(false));
assertEquals(t.leaf1, treeWalker.moveToNode(false));
assertEquals(t.middle1, treeWalker.moveToNode(false));
assertEquals(t.lower3, treeWalker.moveToNode(false));
});
TEST_F('AutomationTreeWalkerUnitTest', 'GetNextNode', function() {
let t = this.getSampleTree();
let treeWalker = new AutomationTreeWalker(
t.middle1, t.middle1, this.getDefaultRestrictions());
let order =
[t.middle1, t.lower1, t.leaf1, t.leaf2, t.leaf3,
t.lower2, t.leaf4, t.leaf5];
let node = t.middle1;
for (let i = 0; i < order.length; i++) {
assertEquals(order[i], node);
node = treeWalker.getNextNode_(node);
}
assertEquals(undefined, node);
});
TEST_F('AutomationTreeWalkerUnitTest', 'GetPreviousNode', function() {
let t = this.getSampleTree();
let treeWalker = new AutomationTreeWalker(
t.leaf5, t.middle1, this.getDefaultRestrictions());
let order =
[t.leaf5, t.leaf4, t.lower2, t.leaf3, t.leaf2,
t.leaf1, t.lower1, t.middle1];
let node = t.leaf5;
for (let i = 0; i < order.length; i++) {
assertEquals(order[i], node);
node = treeWalker.getPreviousNode_(node);
}
assertEquals(undefined, node);
});
TEST_F('AutomationTreeWalkerUnitTest', 'GetYoungestDescendant', function() {
let t = this.getSampleTree();
let treeWalker = new AutomationTreeWalker(
t.root, t.root, this.getDefaultRestrictions());
assertEquals(t.leaf8, treeWalker.getYoungestDescendant_(t.root));
assertEquals(t.leaf7, treeWalker.getYoungestDescendant_(t.upper1));
assertEquals(t.leaf8, treeWalker.getYoungestDescendant_(t.upper2));
assertEquals(t.leaf5, treeWalker.getYoungestDescendant_(t.middle1));
assertEquals(t.leaf7, treeWalker.getYoungestDescendant_(t.middle2));
assertEquals(t.leaf3, treeWalker.getYoungestDescendant_(t.lower1));
assertEquals(t.leaf5, treeWalker.getYoungestDescendant_(t.lower2));
assertEquals(t.leaf7, treeWalker.getYoungestDescendant_(t.lower3));
assertEquals(undefined, treeWalker.getYoungestDescendant_(t.leaf1));
assertEquals(undefined, treeWalker.getYoungestDescendant_(t.leaf2));
assertEquals(undefined, treeWalker.getYoungestDescendant_(t.leaf3));
assertEquals(undefined, treeWalker.getYoungestDescendant_(t.leaf4));
assertEquals(undefined, treeWalker.getYoungestDescendant_(t.leaf5));
assertEquals(undefined, treeWalker.getYoungestDescendant_(t.leaf6));
assertEquals(undefined, treeWalker.getYoungestDescendant_(t.leaf7));
assertEquals(undefined, treeWalker.getYoungestDescendant_(t.leaf8));
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment