// Copyright 2018 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/selection_controller.h" #include #include #include "base/macros.h" #include "base/strings/utf_string_conversions.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/events/event.h" #include "ui/events/event_constants.h" #include "ui/events/types/event_type.h" #include "ui/gfx/render_text.h" #include "ui/views/metrics.h" #include "ui/views/selection_controller_delegate.h" #include "ui/views/style/platform_style.h" namespace views { namespace { const gfx::Point CenterLeft(const gfx::Rect& rect) { return gfx::Point(rect.x(), rect.CenterPoint().y()); } const gfx::Point CenterRight(const gfx::Rect& rect) { return gfx::Point(rect.right(), rect.CenterPoint().y()); } class TestSelectionControllerDelegate : public SelectionControllerDelegate { public: explicit TestSelectionControllerDelegate(gfx::RenderText* render_text) : render_text_(render_text) {} ~TestSelectionControllerDelegate() override = default; gfx::RenderText* GetRenderTextForSelectionController() override { return render_text_; } bool IsReadOnly() const override { return true; } bool SupportsDrag() const override { return true; } bool HasTextBeingDragged() const override { return false; } void SetTextBeingDragged(bool value) override {} int GetViewHeight() const override { return render_text_->GetStringSize().height(); } int GetViewWidth() const override { return render_text_->GetStringSize().width(); } int GetDragSelectionDelay() const override { return 0; } void OnBeforePointerAction() override {} void OnAfterPointerAction(bool text_changed, bool selection_changed) override {} bool PasteSelectionClipboard() override { return false; } void UpdateSelectionClipboard() override {} private: gfx::RenderText* render_text_; DISALLOW_COPY_AND_ASSIGN(TestSelectionControllerDelegate); }; class SelectionControllerTest : public ::testing::Test { public: void SetUp() override { render_text_ = gfx::RenderText::CreateRenderText(); delegate_ = std::make_unique(render_text_.get()); controller_ = std::make_unique(delegate_.get()); } SelectionControllerTest() = default; ~SelectionControllerTest() override = default; void SetText(const std::string& text) { render_text_->SetText(base::ASCIIToUTF16(text)); } std::string GetSelectedText() { return base::UTF16ToASCII( render_text_->GetTextFromRange(render_text_->selection())); } void LeftMouseDown(const gfx::Point& location, bool focused = false) { PressMouseButton(location, ui::EF_LEFT_MOUSE_BUTTON, focused); } void LeftMouseUp() { ReleaseMouseButton(ui::EF_LEFT_MOUSE_BUTTON); } void DragMouse(const gfx::Point& location) { mouse_location_ = location; controller_->OnMouseDragged(ui::MouseEvent(ui::ET_MOUSE_DRAGGED, location, location, last_event_time_, mouse_flags_, 0)); } void RightMouseDown(const gfx::Point& location, bool focused = false) { PressMouseButton(location, ui::EF_RIGHT_MOUSE_BUTTON, focused); } void RightMouseUp() { ReleaseMouseButton(ui::EF_RIGHT_MOUSE_BUTTON); } const gfx::Rect BoundsOfChar(int index) { return render_text_->GetSubstringBounds(gfx::Range(index, index + 1))[0]; } gfx::Point TranslatePointX(const gfx::Point& point, int delta) { return point + gfx::Vector2d(delta, 0); } private: void PressMouseButton(const gfx::Point& location, int button, bool focused) { DCHECK(!(mouse_flags_ & button)); mouse_flags_ |= button; mouse_location_ = location; // Ensure that mouse presses are spaced apart by at least the double-click // interval to avoid triggering a double-click. last_event_time_ += base::TimeDelta::FromMilliseconds(views::GetDoubleClickInterval() + 1); controller_->OnMousePressed( ui::MouseEvent(ui::ET_MOUSE_PRESSED, location, location, last_event_time_, mouse_flags_, button), false, focused ? SelectionController::InitialFocusStateOnMousePress::kFocused : SelectionController::InitialFocusStateOnMousePress::kUnFocused); } void ReleaseMouseButton(int button) { DCHECK(mouse_flags_ & button); mouse_flags_ &= ~button; controller_->OnMouseReleased( ui::MouseEvent(ui::ET_MOUSE_RELEASED, mouse_location_, mouse_location_, last_event_time_, mouse_flags_, button)); } std::unique_ptr render_text_; std::unique_ptr delegate_; std::unique_ptr controller_; int mouse_flags_ = 0; gfx::Point mouse_location_; base::TimeTicks last_event_time_; DISALLOW_COPY_AND_ASSIGN(SelectionControllerTest); }; TEST_F(SelectionControllerTest, ClickAndDragToSelect) { SetText("abc def"); EXPECT_EQ("", GetSelectedText()); LeftMouseDown(CenterLeft(BoundsOfChar(0))); DragMouse(CenterRight(BoundsOfChar(0))); EXPECT_EQ("a", GetSelectedText()); DragMouse(CenterRight(BoundsOfChar(2))); EXPECT_EQ("abc", GetSelectedText()); LeftMouseUp(); EXPECT_EQ("abc", GetSelectedText()); LeftMouseDown(CenterRight(BoundsOfChar(3))); EXPECT_EQ("", GetSelectedText()); DragMouse(CenterRight(BoundsOfChar(4))); EXPECT_EQ("d", GetSelectedText()); } TEST_F(SelectionControllerTest, RightClickWhenUnfocused) { SetText("abc def"); RightMouseDown(CenterRight(BoundsOfChar(0))); if (PlatformStyle::kSelectAllOnRightClickWhenUnfocused) EXPECT_EQ("abc def", GetSelectedText()); else EXPECT_EQ("", GetSelectedText()); } TEST_F(SelectionControllerTest, RightClickSelectsWord) { SetText("abc def"); RightMouseDown(CenterRight(BoundsOfChar(5)), true); if (PlatformStyle::kSelectWordOnRightClick) EXPECT_EQ("def", GetSelectedText()); else EXPECT_EQ("", GetSelectedText()); } // Regression test for https://crbug.com/856609 TEST_F(SelectionControllerTest, RightClickPastEndDoesntSelectLastWord) { SetText("abc def"); RightMouseDown(CenterRight(BoundsOfChar(6)), true); EXPECT_EQ("", GetSelectedText()); } // Regression test for https://crbug.com/731252 // This test validates that drags which are: // a) Above or below the text, and // b) Past one end of the text // behave properly with regard to RenderText::kDragToEndIfOutsideVerticalBounds. // When that option is true, drags outside the text that are horizontally // "towards" the text should select all of it; when that option is false, those // drags should have no effect. TEST_F(SelectionControllerTest, DragPastEndUsesProperOrigin) { SetText("abc def"); gfx::Point point = BoundsOfChar(6).top_right() + gfx::Vector2d(100, -10); LeftMouseDown(point); EXPECT_EQ("", GetSelectedText()); DragMouse(TranslatePointX(point, -1)); if (gfx::RenderText::kDragToEndIfOutsideVerticalBounds) EXPECT_EQ("abc def", GetSelectedText()); else EXPECT_EQ("", GetSelectedText()); DragMouse(TranslatePointX(point, 1)); EXPECT_EQ("", GetSelectedText()); } } // namespace } // namespace views