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