1 // Copyright 2018 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 "ash/shortcut_viewer/views/keyboard_shortcut_view.h"
6
7 #include <set>
8
9 #include "ash/shortcut_viewer/keyboard_shortcut_viewer_metadata.h"
10 #include "ash/shortcut_viewer/views/keyboard_shortcut_item_view.h"
11 #include "ash/shortcut_viewer/views/ksv_search_box_view.h"
12 #include "ash/test/ash_test_base.h"
13 #include "base/bind.h"
14 #include "base/test/metrics/histogram_tester.h"
15 #include "base/test/task_environment.h"
16 #include "testing/gtest/include/gtest/gtest.h"
17 #include "ui/aura/window.h"
18 #include "ui/compositor/test/test_utils.h"
19 #include "ui/display/display.h"
20 #include "ui/display/screen.h"
21 #include "ui/events/devices/device_data_manager_test_api.h"
22 #include "ui/events/test/event_generator.h"
23 #include "ui/views/controls/textfield/textfield.h"
24 #include "ui/views/test/button_test_api.h"
25 #include "ui/views/widget/widget.h"
26
27 namespace keyboard_shortcut_viewer {
28
29 class KeyboardShortcutViewTest : public ash::AshTestBase {
30 public:
KeyboardShortcutViewTest()31 KeyboardShortcutViewTest()
32 : ash::AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
33 ~KeyboardShortcutViewTest() override = default;
34
Toggle()35 views::Widget* Toggle() { return KeyboardShortcutView::Toggle(GetContext()); }
36
37 // ash::AshTestBase:
SetUp()38 void SetUp() override {
39 ash::AshTestBase::SetUp();
40 // Simulate the complete listing of input devices, required by the viewer.
41 ui::DeviceDataManagerTestApi().OnDeviceListsComplete();
42 }
43
44 protected:
GetTabCount() const45 size_t GetTabCount() const {
46 DCHECK(GetView());
47 return GetView()->GetTabCountForTesting();
48 }
49
50 const std::vector<std::unique_ptr<KeyboardShortcutItemView>>&
GetShortcutViews() const51 GetShortcutViews() const {
52 DCHECK(GetView());
53 return GetView()->GetShortcutViewsForTesting();
54 }
55
GetSearchBoxView()56 KSVSearchBoxView* GetSearchBoxView() {
57 DCHECK(GetView());
58 return GetView()->GetSearchBoxViewForTesting();
59 }
60
GetFoundShortcutItems() const61 const std::vector<KeyboardShortcutItemView*>& GetFoundShortcutItems() const {
62 DCHECK(GetView());
63 return GetView()->GetFoundShortcutItemsForTesting();
64 }
65
KeyPress(ui::KeyboardCode key_code,bool should_insert)66 void KeyPress(ui::KeyboardCode key_code, bool should_insert) {
67 ui::KeyEvent event(ui::ET_KEY_PRESSED, key_code, ui::EF_NONE);
68 GetSearchBoxView()->OnKeyEvent(&event);
69 if (!should_insert)
70 return;
71
72 // Emulates the input method.
73 if (::isalnum(static_cast<int>(key_code))) {
74 base::char16 character = ::tolower(static_cast<int>(key_code));
75 GetSearchBoxView()->search_box()->InsertText(
76 base::string16(1, character));
77 }
78 }
79
80 base::HistogramTester histograms_;
81
82 private:
GetView() const83 KeyboardShortcutView* GetView() const {
84 return KeyboardShortcutView::GetInstanceForTesting();
85 }
86
87 DISALLOW_COPY_AND_ASSIGN(KeyboardShortcutViewTest);
88 };
89
90 // Shows and closes the widget for KeyboardShortcutViewer.
TEST_F(KeyboardShortcutViewTest,ShowAndClose)91 TEST_F(KeyboardShortcutViewTest, ShowAndClose) {
92 // Show the widget.
93 views::Widget* widget = Toggle();
94 EXPECT_TRUE(widget);
95
96 // Cleaning up.
97 widget->CloseNow();
98 }
99
TEST_F(KeyboardShortcutViewTest,StartupTimeHistogram)100 TEST_F(KeyboardShortcutViewTest, StartupTimeHistogram) {
101 views::Widget* widget = Toggle();
102 EXPECT_TRUE(ui::WaitForNextFrameToBePresented(widget->GetCompositor()));
103 histograms_.ExpectTotalCount("Keyboard.ShortcutViewer.StartupTime", 1);
104 widget->CloseNow();
105 }
106
107 // KeyboardShortcutViewer window should be centered in screen.
TEST_F(KeyboardShortcutViewTest,CenterWindowInScreen)108 TEST_F(KeyboardShortcutViewTest, CenterWindowInScreen) {
109 // Show the widget.
110 views::Widget* widget = Toggle();
111 EXPECT_TRUE(widget);
112
113 gfx::Rect root_window_bounds =
114 display::Screen::GetScreen()
115 ->GetDisplayNearestWindow(widget->GetNativeWindow()->GetRootWindow())
116 .work_area();
117 gfx::Rect shortcuts_window_bounds =
118 widget->GetNativeWindow()->GetBoundsInScreen();
119 EXPECT_EQ(root_window_bounds.CenterPoint().x(),
120 shortcuts_window_bounds.CenterPoint().x());
121 EXPECT_EQ(root_window_bounds.CenterPoint().y(),
122 shortcuts_window_bounds.CenterPoint().y());
123
124 // Cleaning up.
125 widget->CloseNow();
126 }
127
128 // Test that the number of side tabs equals to the number of categories.
TEST_F(KeyboardShortcutViewTest,SideTabsCount)129 TEST_F(KeyboardShortcutViewTest, SideTabsCount) {
130 // Show the widget.
131 views::Widget* widget = Toggle();
132
133 size_t category_number = 0;
134 ShortcutCategory current_category = ShortcutCategory::kUnknown;
135 for (const auto& item_view : GetShortcutViews()) {
136 const ShortcutCategory category = item_view->category();
137 if (current_category != category) {
138 DCHECK(current_category < category);
139 ++category_number;
140 current_category = category;
141 }
142 }
143 EXPECT_EQ(GetTabCount(), category_number);
144
145 // Cleaning up.
146 widget->CloseNow();
147 }
148
149 // Test that the top line in two views should be center aligned.
TEST_F(KeyboardShortcutViewTest,TopLineCenterAlignedInItemView)150 TEST_F(KeyboardShortcutViewTest, TopLineCenterAlignedInItemView) {
151 // Show the widget.
152 views::Widget* widget = Toggle();
153
154 for (const auto& item_view : GetShortcutViews()) {
155 // We only initialize the first visible category and other non-visible panes
156 // are deferred initialized.
157 if (item_view->category() != ShortcutCategory::kPopular)
158 continue;
159
160 ASSERT_EQ(2u, item_view->children().size());
161
162 // The top lines in both |description_label_view_| and
163 // |shortcut_label_view_| should be center aligned. Only need to check one
164 // view in the top line, because StyledLabel always center align all the
165 // views in a line.
166 const views::View* description = item_view->children()[0];
167 const views::View* shortcut = item_view->children()[1];
168 EXPECT_EQ(
169 description->children().front()->GetBoundsInScreen().CenterPoint().y(),
170 shortcut->children().front()->GetBoundsInScreen().CenterPoint().y());
171 }
172
173 // Cleaning up.
174 widget->CloseNow();
175 }
176
177 // Test that the focus is on search box when window inits and exits search mode.
TEST_F(KeyboardShortcutViewTest,FocusOnSearchBox)178 TEST_F(KeyboardShortcutViewTest, FocusOnSearchBox) {
179 // Show the widget.
180 views::Widget* widget = Toggle();
181
182 // Case 1: when window creates. The focus should be on search box.
183 EXPECT_TRUE(GetSearchBoxView()->search_box()->HasFocus());
184
185 // Press a key should enter search mode.
186 KeyPress(ui::VKEY_A, /*should_insert=*/true);
187 EXPECT_FALSE(GetSearchBoxView()->back_button()->GetVisible());
188 EXPECT_TRUE(GetSearchBoxView()->close_button()->GetVisible());
189 EXPECT_FALSE(GetSearchBoxView()->search_box()->GetText().empty());
190
191 // Case 2: Exit search mode by clicking |close_button|. The focus should be on
192 // search box.
193 views::test::ButtonTestApi(GetSearchBoxView()->close_button())
194 .NotifyClick(ui::MouseEvent(
195 ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(), base::TimeTicks(),
196 ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
197 EXPECT_TRUE(GetSearchBoxView()->search_box()->GetText().empty());
198 EXPECT_TRUE(GetSearchBoxView()->search_box()->HasFocus());
199
200 // Enter search mode again.
201 KeyPress(ui::VKEY_A, /*should_insert=*/true);
202 EXPECT_FALSE(GetSearchBoxView()->search_box()->GetText().empty());
203
204 // Case 3: Exit search mode by pressing |VKEY_ESCAPE|. The focus should be on
205 // search box.
206 KeyPress(ui::VKEY_ESCAPE, /*should_insert=*/false);
207 EXPECT_TRUE(GetSearchBoxView()->search_box()->GetText().empty());
208 EXPECT_TRUE(GetSearchBoxView()->search_box()->HasFocus());
209
210 // Cleaning up.
211 widget->CloseNow();
212 }
213
214 // Test that the window can be closed by accelerator.
TEST_F(KeyboardShortcutViewTest,CloseWindowByAccelerator)215 TEST_F(KeyboardShortcutViewTest, CloseWindowByAccelerator) {
216 // Show the widget.
217 views::Widget* widget = Toggle();
218 EXPECT_FALSE(widget->IsClosed());
219
220 ui::test::EventGenerator* event_generator = GetEventGenerator();
221 event_generator->PressKey(ui::VKEY_W, ui::EF_CONTROL_DOWN);
222 EXPECT_TRUE(widget->IsClosed());
223 }
224
225 // Test that the window can be activated or closed by toggling.
TEST_F(KeyboardShortcutViewTest,ToggleWindow)226 TEST_F(KeyboardShortcutViewTest, ToggleWindow) {
227 // Show the widget.
228 views::Widget* widget = Toggle();
229 EXPECT_FALSE(widget->IsClosed());
230
231 // Call |Toggle()| to activate the inactive widget.
232 EXPECT_TRUE(widget->IsActive());
233 widget->Deactivate();
234 EXPECT_FALSE(widget->IsActive());
235 Toggle();
236 EXPECT_TRUE(widget->IsActive());
237
238 // Call |Toggle()| to close the active widget.
239 Toggle();
240 EXPECT_TRUE(widget->IsClosed());
241 }
242
243 // Test that the sub-labels of the |description_label_view| in the search
244 // results page have the same height and are vertically aligned.
TEST_F(KeyboardShortcutViewTest,ShouldAlignSubLabelsInSearchResults)245 TEST_F(KeyboardShortcutViewTest, ShouldAlignSubLabelsInSearchResults) {
246 // Show the widget.
247 Toggle();
248
249 EXPECT_TRUE(GetFoundShortcutItems().empty());
250 // Type a letter and show the search results.
251 KeyPress(ui::VKEY_A, /*should_insert=*/true);
252 auto time_out = base::TimeDelta::FromMilliseconds(300);
253 task_environment()->FastForwardBy(time_out);
254 base::RunLoop().RunUntilIdle();
255 EXPECT_FALSE(GetFoundShortcutItems().empty());
256
257 for (const auto* item_view : GetFoundShortcutItems()) {
258 ASSERT_EQ(2u, item_view->children().size());
259
260 const views::View* description = item_view->children()[0];
261 // Skip if it only has one label.
262 if (description->children().size() == 1)
263 continue;
264
265 // The sub-labels in |description_label_view_| have the same height and are
266 // vertically aligned in each line.
267 int height = 0;
268 int center_y = 0;
269 for (const auto* child : description->children()) {
270 // The first view in each line.
271 if (child->bounds().x() == 0) {
272 height = child->bounds().height();
273 center_y = child->bounds().CenterPoint().y();
274 continue;
275 }
276
277 EXPECT_EQ(height, child->GetPreferredSize().height());
278 EXPECT_EQ(center_y, child->bounds().CenterPoint().y());
279 }
280 }
281 }
282
283 } // namespace keyboard_shortcut_viewer
284