blob: 39d0657ef24ead497c3d10faa1e68a9a3e0e8160 [file] [log] [blame]
// Copyright 2014 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.
#include "ui/views/accessibility/view_ax_platform_node_delegate.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/strings/utf_string_conversions.h"
#include "base/test/gtest_util.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/platform/ax_platform_node.h"
#include "ui/accessibility/platform/ax_platform_node_base.h"
#include "ui/base/models/table_model.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/accessibility/ax_aura_obj_cache.h"
#include "ui/views/accessibility/ax_aura_obj_wrapper.h"
#include "ui/views/accessibility/ax_event_manager.h"
#include "ui/views/accessibility/ax_event_observer.h"
#include "ui/views/accessibility/ax_widget_obj_wrapper.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/table/table_view.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/widget/widget.h"
namespace views {
namespace test {
namespace {
class TestButton : public Button {
public:
TestButton() : Button(Button::PressedCallback()) {}
TestButton(const TestButton&) = delete;
TestButton& operator=(const TestButton&) = delete;
~TestButton() override = default;
};
class TestAXEventObserver : public AXEventObserver {
public:
explicit TestAXEventObserver(AXAuraObjCache* cache) : cache_(cache) {
AXEventManager::Get()->AddObserver(this);
}
TestAXEventObserver(const TestAXEventObserver&) = delete;
TestAXEventObserver& operator=(const TestAXEventObserver&) = delete;
~TestAXEventObserver() override {
AXEventManager::Get()->RemoveObserver(this);
}
// AXEventObserver:
void OnViewEvent(View* view, ax::mojom::Event event_type) override {
std::vector<AXAuraObjWrapper*> out_children;
AXAuraObjWrapper* ax_obj = cache_->GetOrCreate(view->GetWidget());
ax_obj->GetChildren(&out_children);
}
private:
AXAuraObjCache* cache_;
};
} // namespace
class TestTableModel : public ui::TableModel {
public:
TestTableModel() = default;
// ui::TableModel:
int RowCount() override { return 10; }
base::string16 GetText(int row, int column_id) override {
if (row == -1)
return base::string16();
const char* const cells[5][4] = {
{"Orange", "Orange", "South america", "$5"},
{"Apple", "Green", "Canada", "$3"},
{"Blue berries", "Blue", "Mexico", "$10.3"},
{"Strawberries", "Red", "California", "$7"},
{"Cantaloupe", "Orange", "South america", "$5"},
};
return base::ASCIIToUTF16(cells[row % 5][column_id]);
}
void SetObserver(ui::TableModelObserver* observer) override {}
private:
DISALLOW_COPY_AND_ASSIGN(TestTableModel);
};
class ViewAXPlatformNodeDelegateTest : public ViewsTestBase {
public:
ViewAXPlatformNodeDelegateTest() = default;
ViewAXPlatformNodeDelegateTest(const ViewAXPlatformNodeDelegateTest&) =
delete;
ViewAXPlatformNodeDelegateTest& operator=(
const ViewAXPlatformNodeDelegateTest&) = delete;
~ViewAXPlatformNodeDelegateTest() override = default;
void SetUp() override {
ViewsTestBase::SetUp();
ui::AXPlatformNode::NotifyAddAXModeFlags(ui::kAXModeComplete);
widget_ = new Widget;
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
params.bounds = gfx::Rect(0, 0, 200, 200);
widget_->Init(std::move(params));
button_ = new TestButton();
button_->SetID(NON_DEFAULT_VIEW_ID);
button_->SetSize(gfx::Size(20, 20));
label_ = new Label();
label_->SetID(DEFAULT_VIEW_ID);
button_->AddChildView(label_);
widget_->GetContentsView()->AddChildView(button_);
widget_->Show();
}
void TearDown() override {
if (!widget_->IsClosed())
widget_->Close();
ViewsTestBase::TearDown();
}
ViewAXPlatformNodeDelegate* button_accessibility() {
return static_cast<ViewAXPlatformNodeDelegate*>(
&button_->GetViewAccessibility());
}
ViewAXPlatformNodeDelegate* label_accessibility() {
return static_cast<ViewAXPlatformNodeDelegate*>(
&label_->GetViewAccessibility());
}
ViewAXPlatformNodeDelegate* view_accessibility(View* view) {
return static_cast<ViewAXPlatformNodeDelegate*>(
&view->GetViewAccessibility());
}
bool SetFocused(ViewAXPlatformNodeDelegate* ax_delegate, bool focused) {
ui::AXActionData data;
data.action =
focused ? ax::mojom::Action::kFocus : ax::mojom::Action::kBlur;
return ax_delegate->AccessibilityPerformAction(data);
}
// Sets up a more complicated structure of Views - one parent View with four
// child Views.
View::Views SetUpExtraViews() {
View* parent_view =
widget_->GetContentsView()->AddChildView(std::make_unique<View>());
View::Views views{parent_view};
for (int i = 0; i < 4; i++)
views.push_back(parent_view->AddChildView(std::make_unique<View>()));
return views;
}
// Adds group id information to the first 5 values in |views|.
void SetUpExtraViewsGroups(const View::Views& views) {
// v[0] g1
// | | | |
// v[1] g1 v[2] g1 v[3] g2 v[4]
ASSERT_GE(views.size(), 5u);
views[0]->SetGroup(1);
views[1]->SetGroup(1);
views[2]->SetGroup(1);
views[3]->SetGroup(2);
// Skip views[4] - no group id.
}
// Adds posInSet and setSize overrides to the first 5 values in |views|.
void SetUpExtraViewsSetOverrides(const View::Views& views) {
// v[0] p4 s4
// | | | |
// v[1] p3 s4 v[2] p2 s4 v[3] p- s- v[4] p1 s4
ASSERT_GE(views.size(), 5u);
views[0]->GetViewAccessibility().OverridePosInSet(4, 4);
views[1]->GetViewAccessibility().OverridePosInSet(3, 4);
views[2]->GetViewAccessibility().OverridePosInSet(2, 4);
// Skip views[3] - no override.
views[4]->GetViewAccessibility().OverridePosInSet(1, 4);
}
protected:
const int DEFAULT_VIEW_ID = 0;
const int NON_DEFAULT_VIEW_ID = 1;
Widget* widget_ = nullptr;
Button* button_ = nullptr;
Label* label_ = nullptr;
};
class ViewAXPlatformNodeDelegateTableTest
: public ViewAXPlatformNodeDelegateTest {
public:
void SetUp() override {
ViewAXPlatformNodeDelegateTest::SetUp();
std::vector<ui::TableColumn> columns;
columns.push_back(TestTableColumn(0, "Fruit"));
columns.push_back(TestTableColumn(1, "Color"));
columns.push_back(TestTableColumn(2, "Origin"));
columns.push_back(TestTableColumn(3, "Price"));
model_ = std::make_unique<TestTableModel>();
auto table =
std::make_unique<TableView>(model_.get(), columns, TEXT_ONLY, true);
table_ = table.get();
widget_->GetContentsView()->AddChildView(
TableView::CreateScrollViewWithTable(std::move(table)));
}
ui::TableColumn TestTableColumn(int id, const std::string& title) {
ui::TableColumn column;
column.id = id;
column.title = base::ASCIIToUTF16(title.c_str());
column.sortable = true;
return column;
}
ViewAXPlatformNodeDelegate* table_accessibility() {
return view_accessibility(table_);
}
private:
std::unique_ptr<TestTableModel> model_;
TableView* table_ = nullptr; // Owned by parent.
};
TEST_F(ViewAXPlatformNodeDelegateTest, RoleShouldMatch) {
EXPECT_EQ(ax::mojom::Role::kButton, button_accessibility()->GetData().role);
// Since the label is a subview of |button_|, and the button is keyboard
// focusable, the label is assumed to form part of the button and not have a
// role of its own.
EXPECT_EQ(ax::mojom::Role::kIgnored, label_accessibility()->GetData().role);
// This will happen for all potentially keyboard-focusable Views with
// non-keyboard-focusable children, so if we make the button unfocusable, the
// label will be allowed to have its own role again.
button_->SetFocusBehavior(View::FocusBehavior::NEVER);
EXPECT_EQ(ax::mojom::Role::kStaticText,
label_accessibility()->GetData().role);
}
TEST_F(ViewAXPlatformNodeDelegateTest, BoundsShouldMatch) {
gfx::Rect bounds = gfx::ToEnclosingRect(
button_accessibility()->GetData().relative_bounds.bounds);
gfx::Rect screen_bounds =
button_accessibility()->GetUnclippedScreenBoundsRect();
EXPECT_EQ(button_->GetBoundsInScreen(), bounds);
EXPECT_EQ(screen_bounds, bounds);
}
TEST_F(ViewAXPlatformNodeDelegateTest, LabelIsChildOfButton) {
// Disable focus rings for this test: they introduce extra children that can
// be either before or after the label, which complicates correctness testing.
button_->SetInstallFocusRingOnFocus(false);
// |button_| is focusable, so |label_| (as its child) should be ignored.
EXPECT_NE(View::FocusBehavior::NEVER, button_->GetFocusBehavior());
EXPECT_EQ(1, button_accessibility()->GetChildCount());
EXPECT_EQ(button_->GetNativeViewAccessible(),
label_accessibility()->GetParent());
EXPECT_EQ(ax::mojom::Role::kIgnored, label_accessibility()->GetData().role);
// If |button_| is no longer focusable, |label_| should show up again.
button_->SetFocusBehavior(View::FocusBehavior::NEVER);
EXPECT_EQ(1, button_accessibility()->GetChildCount());
EXPECT_EQ(label_->GetNativeViewAccessible(),
button_accessibility()->ChildAtIndex(0));
EXPECT_EQ(button_->GetNativeViewAccessible(),
label_accessibility()->GetParent());
EXPECT_NE(ax::mojom::Role::kIgnored, label_accessibility()->GetData().role);
}
// Verify Views with invisible ancestors have ax::mojom::State::kInvisible.
TEST_F(ViewAXPlatformNodeDelegateTest, InvisibleViews) {
EXPECT_TRUE(widget_->IsVisible());
EXPECT_FALSE(
button_accessibility()->GetData().HasState(ax::mojom::State::kInvisible));
EXPECT_FALSE(
label_accessibility()->GetData().HasState(ax::mojom::State::kInvisible));
button_->SetVisible(false);
EXPECT_TRUE(
button_accessibility()->GetData().HasState(ax::mojom::State::kInvisible));
EXPECT_TRUE(
label_accessibility()->GetData().HasState(ax::mojom::State::kInvisible));
}
TEST_F(ViewAXPlatformNodeDelegateTest, WritableFocus) {
// Make |button_| focusable, and focus/unfocus it via
// ViewAXPlatformNodeDelegate.
button_->SetFocusBehavior(View::FocusBehavior::ALWAYS);
EXPECT_EQ(nullptr, button_->GetFocusManager()->GetFocusedView());
EXPECT_EQ(nullptr, button_accessibility()->GetFocus());
EXPECT_TRUE(SetFocused(button_accessibility(), true));
EXPECT_EQ(button_, button_->GetFocusManager()->GetFocusedView());
EXPECT_EQ(button_->GetNativeViewAccessible(),
button_accessibility()->GetFocus());
EXPECT_TRUE(SetFocused(button_accessibility(), false));
EXPECT_EQ(nullptr, button_->GetFocusManager()->GetFocusedView());
EXPECT_EQ(nullptr, button_accessibility()->GetFocus());
// If not focusable at all, SetFocused() should return false.
button_->SetEnabled(false);
EXPECT_FALSE(SetFocused(button_accessibility(), true));
}
TEST_F(ViewAXPlatformNodeDelegateTest, GetAuthorUniqueIdDefault) {
ASSERT_EQ(base::WideToUTF16(L""), label_accessibility()->GetAuthorUniqueId());
}
TEST_F(ViewAXPlatformNodeDelegateTest, GetAuthorUniqueIdNonDefault) {
ASSERT_EQ(base::WideToUTF16(L"view_1"),
button_accessibility()->GetAuthorUniqueId());
}
TEST_F(ViewAXPlatformNodeDelegateTest, IsOrderedSet) {
View::Views group_ids = SetUpExtraViews();
SetUpExtraViewsGroups(group_ids);
// Only last element has no group id.
EXPECT_TRUE(view_accessibility(group_ids[0])->IsOrderedSet());
EXPECT_TRUE(view_accessibility(group_ids[1])->IsOrderedSet());
EXPECT_TRUE(view_accessibility(group_ids[2])->IsOrderedSet());
EXPECT_TRUE(view_accessibility(group_ids[3])->IsOrderedSet());
EXPECT_FALSE(view_accessibility(group_ids[4])->IsOrderedSet());
EXPECT_TRUE(view_accessibility(group_ids[0])->IsOrderedSetItem());
EXPECT_TRUE(view_accessibility(group_ids[1])->IsOrderedSetItem());
EXPECT_TRUE(view_accessibility(group_ids[2])->IsOrderedSetItem());
EXPECT_TRUE(view_accessibility(group_ids[3])->IsOrderedSetItem());
EXPECT_FALSE(view_accessibility(group_ids[4])->IsOrderedSetItem());
View::Views overrides = SetUpExtraViews();
SetUpExtraViewsSetOverrides(overrides);
// Only overrides[3] has no override values for setSize/ posInSet.
EXPECT_TRUE(view_accessibility(overrides[0])->IsOrderedSet());
EXPECT_TRUE(view_accessibility(overrides[1])->IsOrderedSet());
EXPECT_TRUE(view_accessibility(overrides[2])->IsOrderedSet());
EXPECT_FALSE(view_accessibility(overrides[3])->IsOrderedSet());
EXPECT_TRUE(view_accessibility(overrides[4])->IsOrderedSet());
EXPECT_TRUE(view_accessibility(overrides[0])->IsOrderedSetItem());
EXPECT_TRUE(view_accessibility(overrides[1])->IsOrderedSetItem());
EXPECT_TRUE(view_accessibility(overrides[2])->IsOrderedSetItem());
EXPECT_FALSE(view_accessibility(overrides[3])->IsOrderedSetItem());
EXPECT_TRUE(view_accessibility(overrides[4])->IsOrderedSetItem());
}
TEST_F(ViewAXPlatformNodeDelegateTest, SetSizeAndPosition) {
// Test Views with group ids.
View::Views group_ids = SetUpExtraViews();
SetUpExtraViewsGroups(group_ids);
EXPECT_EQ(view_accessibility(group_ids[0])->GetSetSize(), 3);
EXPECT_EQ(view_accessibility(group_ids[0])->GetPosInSet(), 1);
EXPECT_EQ(view_accessibility(group_ids[1])->GetSetSize(), 3);
EXPECT_EQ(view_accessibility(group_ids[1])->GetPosInSet(), 2);
EXPECT_EQ(view_accessibility(group_ids[2])->GetSetSize(), 3);
EXPECT_EQ(view_accessibility(group_ids[2])->GetPosInSet(), 3);
EXPECT_EQ(view_accessibility(group_ids[3])->GetSetSize(), 1);
EXPECT_EQ(view_accessibility(group_ids[3])->GetPosInSet(), 1);
EXPECT_FALSE(view_accessibility(group_ids[4])->GetSetSize().has_value());
EXPECT_FALSE(view_accessibility(group_ids[4])->GetPosInSet().has_value());
// Check if a View is ignored, it is not counted in SetSize or PosInSet
group_ids[1]->GetViewAccessibility().OverrideIsIgnored(true);
group_ids[2]->GetViewAccessibility().OverrideIsIgnored(true);
EXPECT_EQ(view_accessibility(group_ids[0])->GetSetSize(), 1);
EXPECT_EQ(view_accessibility(group_ids[0])->GetPosInSet(), 1);
EXPECT_FALSE(view_accessibility(group_ids[1])->GetSetSize().has_value());
EXPECT_FALSE(view_accessibility(group_ids[1])->GetPosInSet().has_value());
EXPECT_FALSE(view_accessibility(group_ids[2])->GetSetSize().has_value());
EXPECT_FALSE(view_accessibility(group_ids[2])->GetPosInSet().has_value());
group_ids[1]->GetViewAccessibility().OverrideIsIgnored(false);
group_ids[2]->GetViewAccessibility().OverrideIsIgnored(false);
// Test Views with setSize/ posInSet override values set.
View::Views overrides = SetUpExtraViews();
SetUpExtraViewsSetOverrides(overrides);
EXPECT_EQ(view_accessibility(overrides[0])->GetSetSize(), 4);
EXPECT_EQ(view_accessibility(overrides[0])->GetPosInSet(), 4);
EXPECT_EQ(view_accessibility(overrides[1])->GetSetSize(), 4);
EXPECT_EQ(view_accessibility(overrides[1])->GetPosInSet(), 3);
EXPECT_EQ(view_accessibility(overrides[2])->GetSetSize(), 4);
EXPECT_EQ(view_accessibility(overrides[2])->GetPosInSet(), 2);
EXPECT_FALSE(view_accessibility(overrides[3])->GetSetSize().has_value());
EXPECT_FALSE(view_accessibility(overrides[3])->GetPosInSet().has_value());
EXPECT_EQ(view_accessibility(overrides[4])->GetSetSize(), 4);
EXPECT_EQ(view_accessibility(overrides[4])->GetPosInSet(), 1);
// Test Views with both group ids and setSize/ posInSet override values set.
// Make sure the override values take precedence when both are set.
// Add setSize/ posInSet overrides to the Views with group ids.
SetUpExtraViewsSetOverrides(group_ids);
EXPECT_EQ(view_accessibility(group_ids[0])->GetSetSize(), 4);
EXPECT_EQ(view_accessibility(group_ids[0])->GetPosInSet(), 4);
EXPECT_EQ(view_accessibility(group_ids[1])->GetSetSize(), 4);
EXPECT_EQ(view_accessibility(group_ids[1])->GetPosInSet(), 3);
EXPECT_EQ(view_accessibility(group_ids[2])->GetSetSize(), 4);
EXPECT_EQ(view_accessibility(group_ids[2])->GetPosInSet(), 2);
EXPECT_EQ(view_accessibility(group_ids[3])->GetSetSize(), 1);
EXPECT_EQ(view_accessibility(group_ids[3])->GetPosInSet(), 1);
EXPECT_EQ(view_accessibility(group_ids[4])->GetSetSize(), 4);
EXPECT_EQ(view_accessibility(group_ids[4])->GetPosInSet(), 1);
}
TEST_F(ViewAXPlatformNodeDelegateTest, TreeNavigation) {
// Adds one extra parent view with four child views to our widget. The parent
// view is added as the next sibling of the already present button view.
//
// Widget
// ++Button
// ++++Label
// 0 = ++ParentView
// 1 = ++++ChildView1
// 2 = ++++ChildView2
// 3 = ChildView3
// 4 = ChildView4
View::Views extra_views = SetUpExtraViews();
ViewAXPlatformNodeDelegate* parent_view = view_accessibility(extra_views[0]);
ViewAXPlatformNodeDelegate* child_view_1 = view_accessibility(extra_views[1]);
ViewAXPlatformNodeDelegate* child_view_2 = view_accessibility(extra_views[2]);
ViewAXPlatformNodeDelegate* child_view_3 = view_accessibility(extra_views[3]);
ViewAXPlatformNodeDelegate* child_view_4 = view_accessibility(extra_views[4]);
EXPECT_EQ(view_accessibility(widget_->GetContentsView())->GetNativeObject(),
parent_view->GetParent());
EXPECT_EQ(4, parent_view->GetChildCount());
EXPECT_EQ(2, button_accessibility()->GetIndexInParent());
EXPECT_EQ(3, parent_view->GetIndexInParent());
EXPECT_EQ(child_view_1->GetNativeObject(), parent_view->ChildAtIndex(0));
EXPECT_EQ(child_view_2->GetNativeObject(), parent_view->ChildAtIndex(1));
EXPECT_EQ(child_view_3->GetNativeObject(), parent_view->ChildAtIndex(2));
EXPECT_EQ(child_view_4->GetNativeObject(), parent_view->ChildAtIndex(3));
EXPECT_EQ(nullptr, parent_view->GetNextSibling());
EXPECT_EQ(button_accessibility()->GetNativeObject(),
parent_view->GetPreviousSibling());
EXPECT_EQ(parent_view->GetNativeObject(), child_view_1->GetParent());
EXPECT_EQ(0, child_view_1->GetChildCount());
EXPECT_EQ(0, child_view_1->GetIndexInParent());
EXPECT_EQ(child_view_2->GetNativeObject(), child_view_1->GetNextSibling());
EXPECT_EQ(nullptr, child_view_1->GetPreviousSibling());
EXPECT_EQ(parent_view->GetNativeObject(), child_view_2->GetParent());
EXPECT_EQ(0, child_view_2->GetChildCount());
EXPECT_EQ(1, child_view_2->GetIndexInParent());
EXPECT_EQ(child_view_3->GetNativeObject(), child_view_2->GetNextSibling());
EXPECT_EQ(child_view_1->GetNativeObject(),
child_view_2->GetPreviousSibling());
EXPECT_EQ(parent_view->GetNativeObject(), child_view_3->GetParent());
EXPECT_EQ(0, child_view_3->GetChildCount());
EXPECT_EQ(2, child_view_3->GetIndexInParent());
EXPECT_EQ(child_view_4->GetNativeObject(), child_view_3->GetNextSibling());
EXPECT_EQ(child_view_2->GetNativeObject(),
child_view_3->GetPreviousSibling());
EXPECT_EQ(parent_view->GetNativeObject(), child_view_4->GetParent());
EXPECT_EQ(0, child_view_4->GetChildCount());
EXPECT_EQ(3, child_view_4->GetIndexInParent());
EXPECT_EQ(nullptr, child_view_4->GetNextSibling());
EXPECT_EQ(child_view_3->GetNativeObject(),
child_view_4->GetPreviousSibling());
}
TEST_F(ViewAXPlatformNodeDelegateTest, TreeNavigationWithIgnoredViews) {
// Adds one extra parent view with four child views to our widget. The parent
// view is added as the next sibling of the already present button view.
//
// Widget
// ++Button
// ++++Label
// 0 = ++ParentView
// 1 = ++++ChildView1
// 2 = ++++ChildView2
// 3 = ChildView3
// 4 = ChildView4
View::Views extra_views = SetUpExtraViews();
ViewAXPlatformNodeDelegate* contents_view =
view_accessibility(widget_->GetContentsView());
ViewAXPlatformNodeDelegate* parent_view = view_accessibility(extra_views[0]);
ViewAXPlatformNodeDelegate* child_view_1 = view_accessibility(extra_views[1]);
ViewAXPlatformNodeDelegate* child_view_2 = view_accessibility(extra_views[2]);
ViewAXPlatformNodeDelegate* child_view_3 = view_accessibility(extra_views[3]);
ViewAXPlatformNodeDelegate* child_view_4 = view_accessibility(extra_views[4]);
// Mark the parent view and the second child view as ignored.
parent_view->OverrideIsIgnored(true);
child_view_2->OverrideIsIgnored(true);
EXPECT_EQ(contents_view->GetNativeObject(), parent_view->GetParent());
EXPECT_EQ(3, parent_view->GetChildCount());
EXPECT_EQ(2, button_accessibility()->GetIndexInParent());
EXPECT_EQ(-1, parent_view->GetIndexInParent());
EXPECT_EQ(child_view_1->GetNativeObject(), parent_view->ChildAtIndex(0));
EXPECT_EQ(child_view_3->GetNativeObject(), parent_view->ChildAtIndex(1));
EXPECT_EQ(child_view_4->GetNativeObject(), parent_view->ChildAtIndex(2));
EXPECT_EQ(button_accessibility()->GetNativeObject(),
contents_view->ChildAtIndex(2));
EXPECT_EQ(child_view_1->GetNativeObject(), contents_view->ChildAtIndex(3));
EXPECT_EQ(child_view_3->GetNativeObject(), contents_view->ChildAtIndex(4));
EXPECT_EQ(child_view_4->GetNativeObject(), contents_view->ChildAtIndex(5));
EXPECT_EQ(nullptr, parent_view->GetNextSibling());
EXPECT_EQ(nullptr, parent_view->GetPreviousSibling());
EXPECT_EQ(contents_view->GetNativeObject(), child_view_1->GetParent());
EXPECT_EQ(0, child_view_1->GetChildCount());
EXPECT_EQ(3, child_view_1->GetIndexInParent());
EXPECT_EQ(child_view_3->GetNativeObject(), child_view_1->GetNextSibling());
EXPECT_EQ(button_accessibility()->GetNativeObject(),
child_view_1->GetPreviousSibling());
EXPECT_EQ(contents_view->GetNativeObject(), child_view_2->GetParent());
EXPECT_EQ(0, child_view_2->GetChildCount());
EXPECT_EQ(-1, child_view_2->GetIndexInParent());
EXPECT_EQ(nullptr, child_view_2->GetNextSibling());
EXPECT_EQ(nullptr, child_view_2->GetPreviousSibling());
EXPECT_EQ(contents_view->GetNativeObject(), child_view_3->GetParent());
EXPECT_EQ(0, child_view_3->GetChildCount());
EXPECT_EQ(4, child_view_3->GetIndexInParent());
EXPECT_EQ(child_view_4->GetNativeObject(), child_view_3->GetNextSibling());
EXPECT_EQ(child_view_1->GetNativeObject(),
child_view_3->GetPreviousSibling());
EXPECT_EQ(contents_view->GetNativeObject(), child_view_4->GetParent());
EXPECT_EQ(0, child_view_4->GetChildCount());
EXPECT_EQ(5, child_view_4->GetIndexInParent());
EXPECT_EQ(nullptr, child_view_4->GetNextSibling());
EXPECT_EQ(child_view_3->GetNativeObject(),
child_view_4->GetPreviousSibling());
}
TEST_F(ViewAXPlatformNodeDelegateTest, OverrideHasPopup) {
View::Views view_ids = SetUpExtraViews();
view_ids[1]->GetViewAccessibility().OverrideHasPopup(
ax::mojom::HasPopup::kTrue);
view_ids[2]->GetViewAccessibility().OverrideHasPopup(
ax::mojom::HasPopup::kMenu);
ui::AXNodeData node_data_0;
view_ids[0]->GetViewAccessibility().GetAccessibleNodeData(&node_data_0);
EXPECT_EQ(node_data_0.GetHasPopup(), ax::mojom::HasPopup::kFalse);
ui::AXNodeData node_data_1;
view_ids[1]->GetViewAccessibility().GetAccessibleNodeData(&node_data_1);
EXPECT_EQ(node_data_1.GetHasPopup(), ax::mojom::HasPopup::kTrue);
ui::AXNodeData node_data_2;
view_ids[2]->GetViewAccessibility().GetAccessibleNodeData(&node_data_2);
EXPECT_EQ(node_data_2.GetHasPopup(), ax::mojom::HasPopup::kMenu);
}
TEST_F(ViewAXPlatformNodeDelegateTest, FocusOnMenuClose) {
// Set Focus on the button
button_->SetFocusBehavior(View::FocusBehavior::ALWAYS);
EXPECT_EQ(nullptr, button_->GetFocusManager()->GetFocusedView());
EXPECT_EQ(nullptr, button_accessibility()->GetFocus());
EXPECT_TRUE(SetFocused(button_accessibility(), true));
EXPECT_EQ(button_->GetNativeViewAccessible(),
button_accessibility()->GetFocus());
// Fire FocusAfterMenuClose event on the button.
base::RunLoop run_loop;
ui::AXPlatformNodeBase::SetOnNotifyEventCallbackForTesting(
ax::mojom::Event::kFocusAfterMenuClose, run_loop.QuitClosure());
button_accessibility()->FireFocusAfterMenuClose();
run_loop.Run();
EXPECT_EQ(button_->GetNativeViewAccessible(),
button_accessibility()->GetFocus());
}
TEST_F(ViewAXPlatformNodeDelegateTableTest, TableHasHeader) {
EXPECT_TRUE(table_accessibility()->GetTableHasColumnOrRowHeaderNode());
EXPECT_EQ(size_t{4}, table_accessibility()->GetColHeaderNodeIds().size());
EXPECT_TRUE(table_accessibility()->GetColHeaderNodeIds(5).empty());
}
TEST_F(ViewAXPlatformNodeDelegateTableTest, TableHasCell) {
EXPECT_NE(base::nullopt, table_accessibility()->GetCellId(0, 0));
EXPECT_NE(base::nullopt, table_accessibility()->GetCellId(0, 3));
EXPECT_NE(base::nullopt, table_accessibility()->GetCellId(9, 3));
EXPECT_DCHECK_DEATH(table_accessibility()->GetCellId(-1, 0));
EXPECT_DCHECK_DEATH(table_accessibility()->GetCellId(0, -1));
EXPECT_DCHECK_DEATH(table_accessibility()->GetCellId(10, 0));
EXPECT_DCHECK_DEATH(table_accessibility()->GetCellId(0, 4));
}
#if defined(USE_AURA)
class DerivedTestView : public View {
public:
DerivedTestView() = default;
~DerivedTestView() override = default;
void OnBlur() override { SetVisible(false); }
};
using AXViewTest = ViewsTestBase;
// Check if the destruction of the widget ends successfully if |view|'s
// visibility changed during destruction.
TEST_F(AXViewTest, LayoutCalledInvalidateRootView) {
// TODO(jamescook): Construct a real AutomationManagerAura rather than using
// this observer to simulate it.
AXAuraObjCache cache;
TestAXEventObserver observer(&cache);
std::unique_ptr<Widget> widget(new Widget);
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget->Init(std::move(params));
widget->Show();
View* root = widget->GetRootView();
DerivedTestView* parent = new DerivedTestView();
DerivedTestView* child = new DerivedTestView();
root->AddChildView(parent);
parent->AddChildView(child);
child->SetFocusBehavior(DerivedTestView::FocusBehavior::ALWAYS);
parent->SetFocusBehavior(DerivedTestView::FocusBehavior::ALWAYS);
root->SetFocusBehavior(DerivedTestView::FocusBehavior::ALWAYS);
parent->RequestFocus();
// During the destruction of parent, OnBlur will be called and change the
// visibility to false.
parent->SetVisible(true);
cache.GetOrCreate(widget.get());
}
#endif
} // namespace test
} // namespace views