1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "base/logging.h" 6 #include "testing/gmock/include/gmock/gmock.h" 7 #include "testing/gtest/include/gtest/gtest.h" 8 #include "ui/views/controls/single_split_view.h" 9 #include "ui/views/controls/single_split_view_listener.h" 10 11 using ::testing::_; 12 using ::testing::Return; 13 14 namespace { 15 16 static void VerifySplitViewLayout(const views::SingleSplitView& split) { 17 ASSERT_EQ(2, split.child_count()); 18 19 const views::View* leading = split.child_at(0); 20 const views::View* trailing = split.child_at(1); 21 22 if (split.bounds().IsEmpty()) { 23 EXPECT_TRUE(leading->bounds().IsEmpty()); 24 EXPECT_TRUE(trailing->bounds().IsEmpty()); 25 return; 26 } 27 28 EXPECT_FALSE(leading->bounds().IsEmpty()); 29 EXPECT_FALSE(trailing->bounds().IsEmpty()); 30 EXPECT_FALSE(leading->bounds().Intersects(trailing->bounds())); 31 32 if (split.orientation() == views::SingleSplitView::HORIZONTAL_SPLIT) { 33 EXPECT_EQ(leading->bounds().height(), split.bounds().height()); 34 EXPECT_EQ(trailing->bounds().height(), split.bounds().height()); 35 EXPECT_LT(leading->bounds().width() + trailing->bounds().width(), 36 split.bounds().width()); 37 } else if (split.orientation() == views::SingleSplitView::VERTICAL_SPLIT) { 38 EXPECT_EQ(leading->bounds().width(), split.bounds().width()); 39 EXPECT_EQ(trailing->bounds().width(), split.bounds().width()); 40 EXPECT_LT(leading->bounds().height() + trailing->bounds().height(), 41 split.bounds().height()); 42 } else { 43 NOTREACHED(); 44 } 45 } 46 47 class MockObserver : public views::SingleSplitViewListener { 48 public: 49 MOCK_METHOD1(SplitHandleMoved, bool(views::SingleSplitView*)); 50 }; 51 52 class MinimumSizedView: public views::View { 53 public: 54 MinimumSizedView(gfx::Size min_size) : min_size_(min_size) {} 55 56 private: 57 gfx::Size min_size_; 58 virtual gfx::Size GetMinimumSize() OVERRIDE; 59 }; 60 61 gfx::Size MinimumSizedView::GetMinimumSize() { 62 return min_size_; 63 } 64 65 } // namespace 66 67 namespace views { 68 69 TEST(SingleSplitViewTest, Resize) { 70 // Test cases to iterate through for horizontal and vertical split views. 71 struct TestCase { 72 // Split view resize policy for this test case. 73 bool resize_leading_on_bounds_change; 74 // Split view size to set. 75 int primary_axis_size; 76 int secondary_axis_size; 77 // Expected divider offset. 78 int divider_offset; 79 } test_cases[] = { 80 // The initial split size is 100x100, divider at 33. 81 { true, 100, 100, 33 }, 82 // Grow the split view, leading view should grow. 83 { true, 1000, 100, 933 }, 84 // Shrink the split view, leading view should shrink. 85 { true, 200, 100, 133 }, 86 // Minimize the split view, divider should not move. 87 { true, 0, 0, 133 }, 88 // Restore the split view, divider should not move. 89 { false, 500, 100, 133 }, 90 // Resize the split view by secondary axis, divider should not move. 91 { false, 500, 600, 133 } 92 }; 93 94 SingleSplitView::Orientation orientations[] = { 95 SingleSplitView::HORIZONTAL_SPLIT, 96 SingleSplitView::VERTICAL_SPLIT 97 }; 98 99 for (size_t orientation = 0; orientation < arraysize(orientations); 100 ++orientation) { 101 // Create a split view. 102 SingleSplitView split( 103 new View(), new View(), orientations[orientation], NULL); 104 105 // Set initial size and divider offset. 106 EXPECT_EQ(test_cases[0].primary_axis_size, 107 test_cases[0].secondary_axis_size); 108 split.SetBounds(0, 0, test_cases[0].primary_axis_size, 109 test_cases[0].secondary_axis_size); 110 split.set_divider_offset(test_cases[0].divider_offset); 111 split.Layout(); 112 113 // Run all test cases. 114 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { 115 split.set_resize_leading_on_bounds_change( 116 test_cases[i].resize_leading_on_bounds_change); 117 if (split.orientation() == SingleSplitView::HORIZONTAL_SPLIT) { 118 split.SetBounds(0, 0, test_cases[i].primary_axis_size, 119 test_cases[i].secondary_axis_size); 120 } else { 121 split.SetBounds(0, 0, test_cases[i].secondary_axis_size, 122 test_cases[i].primary_axis_size); 123 } 124 125 EXPECT_EQ(test_cases[i].divider_offset, split.divider_offset()); 126 VerifySplitViewLayout(split); 127 } 128 129 // Special cases, one of the child views is hidden. 130 split.child_at(0)->SetVisible(false); 131 split.Layout(); 132 133 EXPECT_EQ(split.size(), split.child_at(1)->size()); 134 135 split.child_at(0)->SetVisible(true); 136 split.child_at(1)->SetVisible(false); 137 split.Layout(); 138 139 EXPECT_EQ(split.size(), split.child_at(0)->size()); 140 } 141 } 142 143 TEST(SingleSplitViewTest, MouseDrag) { 144 MockObserver observer; 145 const int kMinimumChildSize = 25; 146 MinimumSizedView *child0 = 147 new MinimumSizedView(gfx::Size(5, kMinimumChildSize)); 148 MinimumSizedView *child1 = 149 new MinimumSizedView(gfx::Size(5, kMinimumChildSize)); 150 SingleSplitView split( 151 child0, child1, SingleSplitView::VERTICAL_SPLIT, &observer); 152 153 ON_CALL(observer, SplitHandleMoved(_)) 154 .WillByDefault(Return(true)); 155 // SplitHandleMoved is called for two mouse moves and one mouse capture loss. 156 EXPECT_CALL(observer, SplitHandleMoved(_)) 157 .Times(5); 158 159 const int kTotalSplitSize = 100; 160 split.SetBounds(0, 0, 10, kTotalSplitSize); 161 const int kInitialDividerOffset = 33; 162 const int kMouseOffset = 2; // Mouse offset in the divider. 163 const int kMouseMoveDelta = 7; 164 split.set_divider_offset(kInitialDividerOffset); 165 split.Layout(); 166 167 gfx::Point press_point(7, kInitialDividerOffset + kMouseOffset); 168 ui::MouseEvent mouse_pressed( 169 ui::ET_MOUSE_PRESSED, press_point, press_point, 0); 170 ASSERT_TRUE(split.OnMousePressed(mouse_pressed)); 171 EXPECT_EQ(kInitialDividerOffset, split.divider_offset()); 172 173 // Drag divider to the bottom. 174 gfx::Point drag_1_point( 175 5, kInitialDividerOffset + kMouseOffset + kMouseMoveDelta); 176 ui::MouseEvent mouse_dragged_1( 177 ui::ET_MOUSE_DRAGGED, drag_1_point, drag_1_point, 0); 178 ASSERT_TRUE(split.OnMouseDragged(mouse_dragged_1)); 179 EXPECT_EQ(kInitialDividerOffset + kMouseMoveDelta, split.divider_offset()); 180 181 // Drag divider to the top, beyond first child minimum size. 182 gfx::Point drag_2_point( 183 7, kMinimumChildSize - 5); 184 ui::MouseEvent mouse_dragged_2( 185 ui::ET_MOUSE_DRAGGED, drag_2_point, drag_2_point, 0); 186 ASSERT_TRUE(split.OnMouseDragged(mouse_dragged_2)); 187 EXPECT_EQ(kMinimumChildSize, 188 split.divider_offset()); 189 190 // Drag divider to the bottom, beyond second child minimum size. 191 gfx::Point drag_3_point( 192 7, kTotalSplitSize - kMinimumChildSize + 5); 193 ui::MouseEvent mouse_dragged_3( 194 ui::ET_MOUSE_DRAGGED, drag_3_point, drag_3_point, 0); 195 ASSERT_TRUE(split.OnMouseDragged(mouse_dragged_3)); 196 EXPECT_EQ(kTotalSplitSize - kMinimumChildSize - split.GetDividerSize(), 197 split.divider_offset()); 198 199 // Drag divider between childs' minimum sizes. 200 gfx::Point drag_4_point( 201 6, kInitialDividerOffset + kMouseOffset + kMouseMoveDelta * 2); 202 ui::MouseEvent mouse_dragged_4( 203 ui::ET_MOUSE_DRAGGED, drag_4_point, drag_4_point, 0); 204 ASSERT_TRUE(split.OnMouseDragged(mouse_dragged_4)); 205 EXPECT_EQ(kInitialDividerOffset + kMouseMoveDelta * 2, 206 split.divider_offset()); 207 208 gfx::Point release_point( 209 7, kInitialDividerOffset + kMouseOffset + kMouseMoveDelta * 2); 210 ui::MouseEvent mouse_released( 211 ui::ET_MOUSE_RELEASED, release_point, release_point, 0); 212 split.OnMouseReleased(mouse_released); 213 EXPECT_EQ(kInitialDividerOffset + kMouseMoveDelta * 2, 214 split.divider_offset()); 215 216 // Expect intial offset after a system/user gesture cancels the drag. 217 // This shouldn't occur after mouse release, but it's sufficient for testing. 218 split.OnMouseCaptureLost(); 219 EXPECT_EQ(kInitialDividerOffset, split.divider_offset()); 220 } 221 222 } // namespace views 223