Commit 40e6324b authored by Anastasia Helfinstein's avatar Anastasia Helfinstein Committed by Commit Bot

[Switch Access] Add menu tests

AX-Relnotes: n/a.
Bug: 897365
Change-Id: I9ca877042531bc7d2b57fd27c390a113c63f58b6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2391383Reviewed-by: default avatarAkihiro Ota <akihiroota@chromium.org>
Reviewed-by: default avatarTetsui Ohkubo <tetsui@chromium.org>
Commit-Queue: Anastasia Helfinstein <anastasi@google.com>
Cr-Commit-Position: refs/heads/master@{#812998}
parent 8fab6cd7
......@@ -82,6 +82,8 @@ void SwitchAccessMenuButton::ButtonPressed(views::Button* sender,
void SwitchAccessMenuButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
views::Button::GetAccessibleNodeData(node_data);
node_data->AddStringAttribute(ax::mojom::StringAttribute::kName,
base::UTF16ToUTF8(label_->GetText()));
node_data->AddStringAttribute(ax::mojom::StringAttribute::kValue,
action_name_);
}
......
......@@ -56,18 +56,15 @@ E2ETestBase = class extends testing.Test {
*/
listenUntil(predicate, node, eventType, callback, capture = false) {
callback = this.newCallback(callback);
const listenOnce = true;
const listener = new EventHandler(
node, eventType, callback, {predicate, capture, listenOnce});
listener.start();
if (predicate()) {
listener.stop();
callback();
return;
}
const listener = () => {
if (predicate()) {
node.removeEventListener(eventType, listener, capture);
callback.apply(this, arguments);
}
};
node.addEventListener(eventType, listener, capture);
}
/**
......
......@@ -102,6 +102,7 @@ js2gtest("switch_access_extjs_tests") {
test_type = "extension"
sources = [
"auto_scan_manager_test.js",
"menu_manager_test.js",
"navigation_manager_test.js",
"nodes/basic_node_test.js",
"nodes/desktop_node_test.js",
......
......@@ -35,6 +35,9 @@ class MenuManager {
this.clickHandler_ = new EventHandler(
[], chrome.automation.EventType.CLICKED,
this.onButtonClicked_.bind(this));
/** @private {!function()} */
this.onMenuLoadedForTesting_ = () => {};
}
static get instance() {
......@@ -215,6 +218,7 @@ class MenuManager {
this.clickHandler_.setNodes(this.menuAutomationNode_);
this.clickHandler_.start();
NavigationManager.jumpToSwitchAccessMenu(this.menuAutomationNode_);
this.onMenuLoadedForTesting_();
}
/**
......
// Copyright 2020 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']);
/** Test fixture for the menu manager. */
SwitchAccessMenuManagerTest = class extends SwitchAccessE2ETest {
openMenuForTextField(desktop) {
const textField = desktop.find({role: 'textField'});
// We expect there to be at least one text field onscreen (the omnibar).
assertNotNullNorUndefined(textField, 'Couldn\'t find a text field');
NavigationManager.instance.moveTo_(textField);
MenuManager.enter();
assertTrue(
MenuManager.instance.isMenuOpen_,
'Menu manager should be marked as open');
assertTrue(
MenuManager.instance.actionNode_.isEquivalentTo(textField),
'Menu is open for the wrong node');
}
// Callback will be called when the menu has loaded, and Switch Access focus
// has shifted to the menu.
setMenuLoadCallback(callback) {
MenuManager.instance.onMenuLoadedForTesting_ = this.newCallback(callback);
}
waitForMenuClose(callback) {
const closedPredicate = () => {
const node = NavigationManager.desktopNode.find(
{role: 'menu', attributes: {className: 'SwitchAccessMenuView'}});
if (!node || !node.role) {
return true;
}
if (node.state['offscreen'] || node.state['invisible']) {
return true;
}
if (node.location.width === 0 && node.location.height === 0) {
return true;
}
return false;
};
this.waitForPredicate(closedPredicate, this.newCallback(callback));
}
};
TEST_F('SwitchAccessMenuManagerTest', 'Enter', function() {
this.runWithLoadedTree('', (desktop) => {
const manager = MenuManager.instance;
this.setMenuLoadCallback(() => {
assertFalse(
manager.inTextNavigation_, 'Menu should not be in text navigation');
assertNotNullNorUndefined(manager.actionNode_, 'Menu has no action node');
const actionNode = manager.actionNode_;
assertEquals(
actionNode.automationNode.role, 'textField',
'Menu is not open for the textField');
assertGT(
actionNode.actions.length, 1,
'TextField should have more than 1 action available');
const menuNode = manager.menuAutomationNode_;
assertTrue(
RectUtil.close(
menuNode.location, manager.displayedLocation_, /*tolerance=*/ 10),
'Menu should be close to the display location');
assertGT(menuNode.location.width, 0, 'Menu should have a nonzero width');
assertGT(
menuNode.location.height, 0, 'Menu should have a nonzero height');
assertFalse(
!!menuNode.state['offscreen'],
'Menu should not be marked as offscreen');
const interestingChildren =
BasicRootNode.getInterestingChildren(menuNode);
const globalActionCount = 1;
assertEquals(
actionNode.actions.length + globalActionCount,
interestingChildren.length,
'Menu should show all actions for textField (while improved text ' +
'navigation flag is disabled)');
for (let i = 0; i < actionNode.actions.length; i++) {
action = actionNode.actions[i];
button = interestingChildren[i].value;
assertEquals(
action, button,
'Button ' + i + ' ("' + button + '") should be action "' + action +
'"');
}
});
this.openMenuForTextField(desktop);
});
});
TEST_F('SwitchAccessMenuManagerTest', 'Exit', function() {
this.runWithLoadedTree('', (desktop) => {
const manager = MenuManager.instance;
this.setMenuLoadCallback(() => {
MenuManager.exit();
assertFalse(manager.isMenuOpen_, 'Menu should be marked as closed');
assertFalse(
manager.inTextNavigation_, 'Menu should not be in text navigation');
assertNullOrUndefined(
manager.actionNode_, 'Action node should have been reset');
assertNullOrUndefined(
manager.displayedActions_,
'Displayed actions should have been reset');
assertNullOrUndefined(
manager.displayedLocation_,
'Displayed location should have been reset');
const navGroup = NavigationManager.instance.group_.automationNode;
assertNotEquals(
navGroup.className, 'SwitchAccessMenuView',
'Navigation manager did not exit the menu');
this.waitForMenuClose();
});
this.openMenuForTextField(desktop);
});
});
TEST_F('SwitchAccessMenuManagerTest', 'Navigation', function() {
const website = `<button id="test" aria-pressed=false>First Button</button>
<script>
let state = false;
let button = document.getElementById('test');
button.onclick = () => {
state = !state;
button.setAttribute('aria-pressed', state);
};
</script>`;
this.runWithLoadedTree(website, (desktop) => {
const manager = MenuManager.instance;
const navigator = NavigationManager.instance;
const button = this.findNodeById('test');
button.addEventListener('checkedStateChanged', this.newCallback((event) => {
assertEquals(
button.htmlAttributes.id, event.target.htmlAttributes.id,
'Checked state changed on unexpected node');
}));
navigator.moveTo_(button);
this.setMenuLoadCallback(() => {
assertTrue(
navigator.group_.isEquivalentTo(manager.menuAutomationNode_),
'Navigation should be focused on the menu');
const selectButton = navigator.node_;
assertEquals(
'select', selectButton.automationNode.value,
'The first action in the menu should be select');
NavigationManager.moveForward();
assertEquals(
'settings', navigator.node_.automationNode.value,
'The second action in the menu should be settings');
NavigationManager.moveForward();
assertTrue(
navigator.node_ instanceof BackButtonNode,
'The third element in the menu should be the back button');
NavigationManager.moveForward();
assertTrue(
selectButton.equals(navigator.node_),
'Moving forward from the back button should take us to the first ' +
'action (select)');
// Press the select button.
MenuManager.enter();
// Wait for the menu to close
this.waitForMenuClose(() => {
assertFalse(manager.isMenuOpen_);
});
});
// Force the menu to show with different actions than it would normally.
manager.actionNode_ = NavigationManager.currentNode;
manager.displayMenuWithActions_(['select', 'settings']);
});
});
......@@ -55,18 +55,10 @@ class SwitchAccess {
*/
static findNodeMatching(findParams, foundCallback) {
const desktop = NavigationManager.desktopNode;
// First, check if the node is currently in the tree.
let node = desktop.find(findParams);
if (node) {
foundCallback(node);
return;
}
// If it's not currently in the tree, listen for changes to the desktop
// tree.
// Listen for changes to the desktop tree, in case it's not currently there.
const eventHandler = new EventHandler(
desktop, chrome.automation.EventType.CHILDREN_CHANGED,
null /** callback */);
const onEvent = (event) => {
if (event.target.matches(findParams)) {
// If the event target is the node we're looking for, we've found it.
......@@ -81,9 +73,16 @@ class SwitchAccess {
}
}
};
eventHandler.setCallback(onEvent);
eventHandler.start();
// Check if the node is already in the tree.
let node = desktop.find(findParams);
if (node) {
eventHandler.stop();
foundCallback(node);
return;
}
}
/*
......
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