diff options
Diffstat (limited to 'chromium/ui/views/view_targeter_unittest.cc')
-rw-r--r-- | chromium/ui/views/view_targeter_unittest.cc | 386 |
1 files changed, 386 insertions, 0 deletions
diff --git a/chromium/ui/views/view_targeter_unittest.cc b/chromium/ui/views/view_targeter_unittest.cc new file mode 100644 index 00000000000..54a28b13a32 --- /dev/null +++ b/chromium/ui/views/view_targeter_unittest.cc @@ -0,0 +1,386 @@ +// 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/view_targeter.h" + +#include "ui/events/event_targeter.h" +#include "ui/events/event_utils.h" +#include "ui/gfx/path.h" +#include "ui/views/masked_view_targeter.h" +#include "ui/views/test/views_test_base.h" +#include "ui/views/widget/root_view.h" + +namespace views { + +// A class used to define a triangular-shaped hit test mask on a View. +class TestMaskedViewTargeter : public MaskedViewTargeter { + public: + explicit TestMaskedViewTargeter(View* masked_view) + : MaskedViewTargeter(masked_view) {} + virtual ~TestMaskedViewTargeter() {} + + private: + virtual bool GetHitTestMask(const View* view, + gfx::Path* mask) const OVERRIDE { + SkScalar w = SkIntToScalar(view->width()); + SkScalar h = SkIntToScalar(view->height()); + + // Create a triangular mask within the bounds of |view|. + mask->moveTo(w / 2, 0); + mask->lineTo(w, h); + mask->lineTo(0, h); + mask->close(); + + return true; + } + + DISALLOW_COPY_AND_ASSIGN(TestMaskedViewTargeter); +}; + +// A derived class of View used for testing purposes. +class TestingView : public View { + public: + TestingView() : can_process_events_within_subtree_(true) {} + virtual ~TestingView() {} + + // Reset all test state. + void Reset() { can_process_events_within_subtree_ = true; } + + void set_can_process_events_within_subtree(bool can_process) { + can_process_events_within_subtree_ = can_process; + } + + // View: + virtual bool CanProcessEventsWithinSubtree() const OVERRIDE { + return can_process_events_within_subtree_; + } + + private: + // Value to return from CanProcessEventsWithinSubtree(). + bool can_process_events_within_subtree_; + + DISALLOW_COPY_AND_ASSIGN(TestingView); +}; + +namespace test { + +typedef ViewsTestBase ViewTargeterTest; + +// Verifies that the the functions ViewTargeter::FindTargetForEvent() +// and ViewTargeter::FindNextBestTarget() are implemented correctly +// for key events. +TEST_F(ViewTargeterTest, ViewTargeterForKeyEvents) { + Widget widget; + Widget::InitParams init_params = + CreateParams(Widget::InitParams::TYPE_POPUP); + init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + widget.Init(init_params); + + View* content = new View; + View* child = new View; + View* grandchild = new View; + + widget.SetContentsView(content); + content->AddChildView(child); + child->AddChildView(grandchild); + + grandchild->SetFocusable(true); + grandchild->RequestFocus(); + + ui::EventTargeter* targeter = new ViewTargeter(); + internal::RootView* root_view = + static_cast<internal::RootView*>(widget.GetRootView()); + root_view->SetEventTargeter(make_scoped_ptr(targeter)); + + ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_A, 0, true); + + // The focused view should be the initial target of the event. + ui::EventTarget* current_target = targeter->FindTargetForEvent(root_view, + &key_event); + EXPECT_EQ(grandchild, static_cast<View*>(current_target)); + + // Verify that FindNextBestTarget() will return the parent view of the + // argument (and NULL if the argument has no parent view). + current_target = targeter->FindNextBestTarget(grandchild, &key_event); + EXPECT_EQ(child, static_cast<View*>(current_target)); + current_target = targeter->FindNextBestTarget(child, &key_event); + EXPECT_EQ(content, static_cast<View*>(current_target)); + current_target = targeter->FindNextBestTarget(content, &key_event); + EXPECT_EQ(widget.GetRootView(), static_cast<View*>(current_target)); + current_target = targeter->FindNextBestTarget(widget.GetRootView(), + &key_event); + EXPECT_EQ(NULL, static_cast<View*>(current_target)); +} + +// Verifies that the the functions ViewTargeter::FindTargetForEvent() +// and ViewTargeter::FindNextBestTarget() are implemented correctly +// for scroll events. +TEST_F(ViewTargeterTest, ViewTargeterForScrollEvents) { + Widget widget; + Widget::InitParams init_params = + CreateParams(Widget::InitParams::TYPE_POPUP); + init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + init_params.bounds = gfx::Rect(0, 0, 200, 200); + widget.Init(init_params); + + // The coordinates used for SetBounds() are in the parent coordinate space. + View* content = new View; + content->SetBounds(0, 0, 100, 100); + View* child = new View; + child->SetBounds(50, 50, 20, 20); + View* grandchild = new View; + grandchild->SetBounds(0, 0, 5, 5); + + widget.SetContentsView(content); + content->AddChildView(child); + child->AddChildView(grandchild); + + ui::EventTargeter* targeter = new ViewTargeter(); + internal::RootView* root_view = + static_cast<internal::RootView*>(widget.GetRootView()); + root_view->SetEventTargeter(make_scoped_ptr(targeter)); + + // The event falls within the bounds of |child| and |content| but not + // |grandchild|, so |child| should be the initial target for the event. + ui::ScrollEvent scroll(ui::ET_SCROLL, + gfx::Point(60, 60), + ui::EventTimeForNow(), + 0, + 0, 3, + 0, 3, + 2); + ui::EventTarget* current_target = targeter->FindTargetForEvent(root_view, + &scroll); + EXPECT_EQ(child, static_cast<View*>(current_target)); + + // Verify that FindNextBestTarget() will return the parent view of the + // argument (and NULL if the argument has no parent view). + current_target = targeter->FindNextBestTarget(child, &scroll); + EXPECT_EQ(content, static_cast<View*>(current_target)); + current_target = targeter->FindNextBestTarget(content, &scroll); + EXPECT_EQ(widget.GetRootView(), static_cast<View*>(current_target)); + current_target = targeter->FindNextBestTarget(widget.GetRootView(), + &scroll); + EXPECT_EQ(NULL, static_cast<View*>(current_target)); + + // The event falls outside of the original specified bounds of |content|, + // |child|, and |grandchild|. But since |content| is the contents view, + // and contents views are resized to fill the entire area of the root + // view, the event's initial target should still be |content|. + scroll = ui::ScrollEvent(ui::ET_SCROLL, + gfx::Point(150, 150), + ui::EventTimeForNow(), + 0, + 0, 3, + 0, 3, + 2); + current_target = targeter->FindTargetForEvent(root_view, &scroll); + EXPECT_EQ(content, static_cast<View*>(current_target)); +} + +// Tests the basic functionality of the method +// ViewTargeter::SubtreeShouldBeExploredForEvent(). +TEST_F(ViewTargeterTest, SubtreeShouldBeExploredForEvent) { + Widget widget; + Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP); + params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params.bounds = gfx::Rect(0, 0, 650, 650); + widget.Init(params); + + ui::EventTargeter* targeter = new ViewTargeter(); + internal::RootView* root_view = + static_cast<internal::RootView*>(widget.GetRootView()); + root_view->SetEventTargeter(make_scoped_ptr(targeter)); + + // The coordinates used for SetBounds() are in the parent coordinate space. + View v1, v2, v3; + v1.SetBounds(0, 0, 300, 300); + v2.SetBounds(100, 100, 100, 100); + v3.SetBounds(0, 0, 10, 10); + v3.SetVisible(false); + root_view->AddChildView(&v1); + v1.AddChildView(&v2); + v2.AddChildView(&v3); + + // Note that the coordinates used below are in |v1|'s coordinate space, + // and that SubtreeShouldBeExploredForEvent() expects the event location + // to be in the coordinate space of the target's parent. |v1| and + // its parent share a common coordinate space. + + // Event located within |v1| only. + gfx::Point point(10, 10); + ui::MouseEvent event(ui::ET_MOUSE_PRESSED, point, point, + ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON); + EXPECT_TRUE(targeter->SubtreeShouldBeExploredForEvent(&v1, event)); + EXPECT_FALSE(targeter->SubtreeShouldBeExploredForEvent(&v2, event)); + v1.ConvertEventToTarget(&v2, &event); + EXPECT_FALSE(targeter->SubtreeShouldBeExploredForEvent(&v3, event)); + + // Event located within |v1| and |v2| only. + event.set_location(gfx::Point(150, 150)); + EXPECT_TRUE(targeter->SubtreeShouldBeExploredForEvent(&v1, event)); + EXPECT_TRUE(targeter->SubtreeShouldBeExploredForEvent(&v2, event)); + v1.ConvertEventToTarget(&v2, &event); + EXPECT_FALSE(targeter->SubtreeShouldBeExploredForEvent(&v3, event)); + + // Event located within |v1|, |v2|, and |v3|. Note that |v3| is not + // visible, so it cannot handle the event. + event.set_location(gfx::Point(105, 105)); + EXPECT_TRUE(targeter->SubtreeShouldBeExploredForEvent(&v1, event)); + EXPECT_TRUE(targeter->SubtreeShouldBeExploredForEvent(&v2, event)); + v1.ConvertEventToTarget(&v2, &event); + EXPECT_FALSE(targeter->SubtreeShouldBeExploredForEvent(&v3, event)); + + // Event located outside the bounds of all views. + event.set_location(gfx::Point(400, 400)); + EXPECT_FALSE(targeter->SubtreeShouldBeExploredForEvent(&v1, event)); + EXPECT_FALSE(targeter->SubtreeShouldBeExploredForEvent(&v2, event)); + v1.ConvertEventToTarget(&v2, &event); + EXPECT_FALSE(targeter->SubtreeShouldBeExploredForEvent(&v3, event)); + + // TODO(tdanderson): Move the hit-testing unit tests out of view_unittest + // and into here. See crbug.com/355425. +} + +// Tests that FindTargetForEvent() returns the correct target when some +// views in the view tree return false when CanProcessEventsWithinSubtree() +// is called on them. +TEST_F(ViewTargeterTest, CanProcessEventsWithinSubtree) { + Widget widget; + Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP); + params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params.bounds = gfx::Rect(0, 0, 650, 650); + widget.Init(params); + + ui::EventTargeter* targeter = new ViewTargeter(); + internal::RootView* root_view = + static_cast<internal::RootView*>(widget.GetRootView()); + root_view->SetEventTargeter(make_scoped_ptr(targeter)); + + // The coordinates used for SetBounds() are in the parent coordinate space. + TestingView v1, v2, v3; + v1.SetBounds(0, 0, 300, 300); + v2.SetBounds(100, 100, 100, 100); + v3.SetBounds(0, 0, 10, 10); + root_view->AddChildView(&v1); + v1.AddChildView(&v2); + v2.AddChildView(&v3); + + // Note that the coordinates used below are in the coordinate space of + // the root view. + + // Define |scroll| to be (105, 105) (in the coordinate space of the root + // view). This is located within all of |v1|, |v2|, and |v3|. + gfx::Point scroll_point(105, 105); + ui::ScrollEvent scroll( + ui::ET_SCROLL, scroll_point, ui::EventTimeForNow(), 0, 0, 3, 0, 3, 2); + + // If CanProcessEventsWithinSubtree() returns true for each view, + // |scroll| should be targeted at the deepest view in the hierarchy, + // which is |v3|. + ui::EventTarget* current_target = + targeter->FindTargetForEvent(root_view, &scroll); + EXPECT_EQ(&v3, current_target); + + // If CanProcessEventsWithinSubtree() returns |false| when called + // on |v3|, then |v3| cannot be the target of |scroll| (this should + // instead be |v2|). Note we need to reset the location of |scroll| + // because it may have been mutated by the previous call to + // FindTargetForEvent(). + scroll.set_location(scroll_point); + v3.set_can_process_events_within_subtree(false); + current_target = targeter->FindTargetForEvent(root_view, &scroll); + EXPECT_EQ(&v2, current_target); + + // If CanProcessEventsWithinSubtree() returns |false| when called + // on |v2|, then neither |v2| nor |v3| can be the target of |scroll| + // (this should instead be |v1|). + scroll.set_location(scroll_point); + v3.Reset(); + v2.set_can_process_events_within_subtree(false); + current_target = targeter->FindTargetForEvent(root_view, &scroll); + EXPECT_EQ(&v1, current_target); + + // If CanProcessEventsWithinSubtree() returns |false| when called + // on |v1|, then none of |v1|, |v2| or |v3| can be the target of |scroll| + // (this should instead be the root view itself). + scroll.set_location(scroll_point); + v2.Reset(); + v1.set_can_process_events_within_subtree(false); + current_target = targeter->FindTargetForEvent(root_view, &scroll); + EXPECT_EQ(root_view, current_target); + + // TODO(tdanderson): We should also test that targeting works correctly + // with gestures. See crbug.com/375822. +} + +// Tests that FindTargetForEvent() returns the correct target when some +// views in the view tree have a MaskedViewTargeter installed, i.e., +// they have a custom-shaped hit test mask. +TEST_F(ViewTargeterTest, MaskedViewTargeter) { + Widget widget; + Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP); + params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params.bounds = gfx::Rect(0, 0, 650, 650); + widget.Init(params); + + ui::EventTargeter* targeter = new ViewTargeter(); + internal::RootView* root_view = + static_cast<internal::RootView*>(widget.GetRootView()); + root_view->SetEventTargeter(make_scoped_ptr(targeter)); + + // The coordinates used for SetBounds() are in the parent coordinate space. + View masked_view, unmasked_view, masked_child; + masked_view.SetBounds(0, 0, 200, 200); + unmasked_view.SetBounds(300, 0, 300, 300); + masked_child.SetBounds(0, 0, 100, 100); + root_view->AddChildView(&masked_view); + root_view->AddChildView(&unmasked_view); + unmasked_view.AddChildView(&masked_child); + + // Install event targeters of type TestMaskedViewTargeter on the two masked + // views to define their hit test masks. + ui::EventTargeter* masked_targeter = new TestMaskedViewTargeter(&masked_view); + masked_view.SetEventTargeter(make_scoped_ptr(masked_targeter)); + masked_targeter = new TestMaskedViewTargeter(&masked_child); + masked_child.SetEventTargeter(make_scoped_ptr(masked_targeter)); + + // Note that the coordinates used below are in the coordinate space of + // the root view. + + // Event located within the hit test mask of |masked_view|. + ui::ScrollEvent scroll(ui::ET_SCROLL, + gfx::Point(100, 190), + ui::EventTimeForNow(), + 0, + 0, + 3, + 0, + 3, + 2); + ui::EventTarget* current_target = + targeter->FindTargetForEvent(root_view, &scroll); + EXPECT_EQ(&masked_view, static_cast<View*>(current_target)); + + // Event located outside the hit test mask of |masked_view|. + scroll.set_location(gfx::Point(10, 10)); + current_target = targeter->FindTargetForEvent(root_view, &scroll); + EXPECT_EQ(root_view, static_cast<View*>(current_target)); + + // Event located within the hit test mask of |masked_child|. + scroll.set_location(gfx::Point(350, 3)); + current_target = targeter->FindTargetForEvent(root_view, &scroll); + EXPECT_EQ(&masked_child, static_cast<View*>(current_target)); + + // Event located within the hit test mask of |masked_child|. + scroll.set_location(gfx::Point(300, 12)); + current_target = targeter->FindTargetForEvent(root_view, &scroll); + EXPECT_EQ(&unmasked_view, static_cast<View*>(current_target)); + + // TODO(tdanderson): We should also test that targeting of masked views + // works correctly with gestures. See crbug.com/375822. +} + +} // namespace test +} // namespace views |