| // 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 "chrome/browser/ash/input_method/ui/candidate_view.h" |
| |
| #include <stddef.h> |
| |
| #include "base/check.h" |
| #include "base/cxx17_backports.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "ui/accessibility/ax_enums.mojom.h" |
| #include "ui/accessibility/ax_node_data.h" |
| #include "ui/aura/window.h" |
| #include "ui/events/test/event_generator.h" |
| #include "ui/gfx/geometry/insets.h" |
| #include "ui/views/controls/button/button.h" |
| #include "ui/views/layout/box_layout.h" |
| #include "ui/views/layout/fill_layout.h" |
| #include "ui/views/test/ax_event_counter.h" |
| #include "ui/views/test/views_test_base.h" |
| #include "ui/views/widget/widget_delegate.h" |
| |
| namespace ui { |
| namespace ime { |
| namespace { |
| |
| const char* const kDummyCandidates[] = { |
| "candidate1", |
| "candidate2", |
| "candidate3", |
| }; |
| |
| } // namespace |
| |
| class CandidateViewTest : public views::ViewsTestBase { |
| public: |
| CandidateViewTest() = default; |
| |
| CandidateViewTest(const CandidateViewTest&) = delete; |
| CandidateViewTest& operator=(const CandidateViewTest&) = delete; |
| |
| ~CandidateViewTest() override = default; |
| |
| void SetUp() override { |
| views::ViewsTestBase::SetUp(); |
| |
| views::Widget::InitParams init_params( |
| CreateParams(views::Widget::InitParams::TYPE_WINDOW)); |
| |
| init_params.delegate = new views::WidgetDelegateView(); |
| |
| container_ = init_params.delegate->GetContentsView(); |
| container_->SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kVertical)); |
| for (size_t i = 0; i < base::size(kDummyCandidates); ++i) { |
| CandidateView* candidate = new CandidateView( |
| views::Button::PressedCallback(), ui::CandidateWindow::VERTICAL); |
| ui::CandidateWindow::Entry entry; |
| entry.value = base::UTF8ToUTF16(kDummyCandidates[i]); |
| candidate->SetEntry(entry); |
| container_->AddChildView(candidate); |
| } |
| |
| widget_ = new views::Widget(); |
| widget_->Init(std::move(init_params)); |
| widget_->Show(); |
| |
| aura::Window* native_window = widget_->GetNativeWindow(); |
| event_generator_ = std::make_unique<ui::test::EventGenerator>( |
| native_window->GetRootWindow(), native_window); |
| } |
| |
| void TearDown() override { |
| widget_->Close(); |
| |
| views::ViewsTestBase::TearDown(); |
| } |
| |
| protected: |
| CandidateView* GetCandidateAt(size_t index) { |
| return static_cast<CandidateView*>(container_->children()[index]); |
| } |
| |
| size_t GetHighlightedCount() const { |
| const auto& children = container_->children(); |
| return std::count_if( |
| children.cbegin(), children.cend(), |
| [](const views::View* v) { return !!v->background(); }); |
| } |
| |
| int GetHighlightedIndex() const { |
| const auto& children = container_->children(); |
| const auto it = |
| std::find_if(children.cbegin(), children.cend(), |
| [](const views::View* v) { return !!v->background(); }); |
| return (it == children.cend()) ? -1 : std::distance(children.cbegin(), it); |
| } |
| |
| ui::test::EventGenerator* event_generator() { return event_generator_.get(); } |
| |
| private: |
| views::Widget* widget_ = nullptr; |
| views::View* container_ = nullptr; |
| std::unique_ptr<ui::test::EventGenerator> event_generator_; |
| }; |
| |
| TEST_F(CandidateViewTest, MouseHovers) { |
| GetCandidateAt(0)->SetHighlighted(true); |
| |
| EXPECT_EQ(1u, GetHighlightedCount()); |
| EXPECT_EQ(0, GetHighlightedIndex()); |
| |
| // Mouse hover shouldn't change the background. |
| event_generator()->MoveMouseTo( |
| GetCandidateAt(0)->GetBoundsInScreen().CenterPoint()); |
| EXPECT_EQ(1u, GetHighlightedCount()); |
| EXPECT_EQ(0, GetHighlightedIndex()); |
| |
| // Mouse hover shouldn't change the background. |
| event_generator()->MoveMouseTo( |
| GetCandidateAt(1)->GetBoundsInScreen().CenterPoint()); |
| EXPECT_EQ(1u, GetHighlightedCount()); |
| EXPECT_EQ(0, GetHighlightedIndex()); |
| |
| // Mouse hover shouldn't change the background. |
| event_generator()->MoveMouseTo( |
| GetCandidateAt(2)->GetBoundsInScreen().CenterPoint()); |
| EXPECT_EQ(1u, GetHighlightedCount()); |
| EXPECT_EQ(0, GetHighlightedIndex()); |
| } |
| |
| TEST_F(CandidateViewTest, MouseClick) { |
| bool clicked = false; |
| CandidateView* view = GetCandidateAt(1); |
| view->SetCallback( |
| base::BindRepeating([](bool* clicked) { *clicked = true; }, &clicked)); |
| event_generator()->MoveMouseTo(view->GetBoundsInScreen().CenterPoint()); |
| event_generator()->ClickLeftButton(); |
| EXPECT_TRUE(clicked); |
| } |
| |
| TEST_F(CandidateViewTest, ClickAndMove) { |
| GetCandidateAt(0)->SetHighlighted(true); |
| |
| EXPECT_EQ(1u, GetHighlightedCount()); |
| EXPECT_EQ(0, GetHighlightedIndex()); |
| |
| bool clicked = false; |
| CandidateView* view = GetCandidateAt(1); |
| view->SetCallback( |
| base::BindRepeating([](bool* clicked) { *clicked = true; }, &clicked)); |
| event_generator()->MoveMouseTo( |
| GetCandidateAt(2)->GetBoundsInScreen().CenterPoint()); |
| event_generator()->PressLeftButton(); |
| EXPECT_EQ(1u, GetHighlightedCount()); |
| EXPECT_EQ(2, GetHighlightedIndex()); |
| |
| // Highlight follows the drag. |
| event_generator()->MoveMouseTo(view->GetBoundsInScreen().CenterPoint()); |
| EXPECT_EQ(1u, GetHighlightedCount()); |
| EXPECT_EQ(1, GetHighlightedIndex()); |
| |
| event_generator()->MoveMouseTo( |
| GetCandidateAt(0)->GetBoundsInScreen().CenterPoint()); |
| EXPECT_EQ(1u, GetHighlightedCount()); |
| EXPECT_EQ(0, GetHighlightedIndex()); |
| |
| event_generator()->MoveMouseTo(view->GetBoundsInScreen().CenterPoint()); |
| EXPECT_EQ(1u, GetHighlightedCount()); |
| EXPECT_EQ(1, GetHighlightedIndex()); |
| |
| EXPECT_FALSE(clicked); |
| event_generator()->ReleaseLeftButton(); |
| EXPECT_TRUE(clicked); |
| } |
| |
| TEST_F(CandidateViewTest, SetEntryChangesAccessibleName) { |
| CandidateView* view = GetCandidateAt(1); |
| |
| ui::CandidateWindow::Entry entry; |
| entry.value = u"Candidate"; |
| view->SetEntry(entry); |
| EXPECT_EQ(u"Candidate", view->GetAccessibleName()); |
| } |
| |
| TEST_F(CandidateViewTest, SetEntryNotifiesAccessibilityEvent) { |
| views::test::AXEventCounter counter(views::AXEventManager::Get()); |
| CandidateView* view = GetCandidateAt(1); |
| |
| // Calling SetEntry affects the accessible name, so it should notify twice: |
| // once for CandidateView's child label and once for CandidateView itself. |
| EXPECT_EQ(0, counter.GetCount(ax::mojom::Event::kTextChanged)); |
| ui::CandidateWindow::Entry entry; |
| entry.value = u"Candidate"; |
| view->SetEntry(entry); |
| EXPECT_EQ(2, counter.GetCount(ax::mojom::Event::kTextChanged)); |
| } |
| |
| TEST_F(CandidateViewTest, GetAccessibleNodeData) { |
| CandidateView* view = GetCandidateAt(1); |
| |
| ui::CandidateWindow::Entry entry; |
| entry.value = u"Candidate"; |
| view->SetEntry(entry); |
| |
| ui::AXNodeData data; |
| static_cast<views::View*>(view)->GetAccessibleNodeData(&data); |
| |
| EXPECT_EQ(ax::mojom::Role::kImeCandidate, data.role); |
| EXPECT_EQ("Candidate", |
| data.GetStringAttribute(ax::mojom::StringAttribute::kName)); |
| EXPECT_EQ(static_cast<int>(ax::mojom::DefaultActionVerb::kPress), |
| data.GetIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb)); |
| } |
| |
| } // namespace ime |
| } // namespace ui |