1 // Copyright 2017 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/login/ui/login_user_view.h"
6 #include "ash/login/ui/login_display_style.h"
7 #include "ash/login/ui/login_test_base.h"
8 #include "ash/login/ui/login_test_utils.h"
9 #include "base/bind.h"
10 #include "testing/gtest/include/gtest/gtest.h"
11 #include "ui/events/test/event_generator.h"
12 #include "ui/gfx/geometry/rect.h"
13 #include "ui/views/layout/box_layout.h"
14 #include "ui/views/widget/widget.h"
15 
16 namespace ash {
17 
18 namespace {
19 
20 class LoginUserViewUnittest : public LoginTestBase {
21  protected:
22   LoginUserViewUnittest() = default;
23   ~LoginUserViewUnittest() override = default;
24 
25   // Builds a new LoginUserView instance and adds it to |container_|.
AddUserView(LoginDisplayStyle display_style,bool show_dropdown,bool public_account)26   LoginUserView* AddUserView(LoginDisplayStyle display_style,
27                              bool show_dropdown,
28                              bool public_account) {
29     LoginUserView::OnRemoveWarningShown on_remove_warning_shown;
30     LoginUserView::OnRemove on_remove;
31     if (show_dropdown) {
32       on_remove_warning_shown = base::BindRepeating(
33           &LoginUserViewUnittest::OnRemoveWarningShown, base::Unretained(this));
34       on_remove = base::BindRepeating(&LoginUserViewUnittest::OnRemove,
35                                       base::Unretained(this));
36     }
37 
38     auto* view =
39         new LoginUserView(display_style, show_dropdown,
40                           base::BindRepeating(&LoginUserViewUnittest::OnTapped,
41                                               base::Unretained(this)),
42                           on_remove_warning_shown, on_remove);
43 
44     std::string email = "foo@foo.com";
45     LoginUserInfo user =
46         public_account ? CreatePublicAccountUser(email) : CreateUser(email);
47     view->UpdateForUser(user, false /*animate*/);
48     container_->AddChildView(view);
49     widget()->GetContentsView()->Layout();
50     return view;
51   }
52 
53   // LoginTestBase:
SetUp()54   void SetUp() override {
55     LoginTestBase::SetUp();
56 
57     container_ = new views::View();
58     container_->SetLayoutManager(std::make_unique<views::BoxLayout>(
59         views::BoxLayout::Orientation::kVertical));
60 
61     auto* root = new views::View();
62     root->SetLayoutManager(std::make_unique<views::BoxLayout>(
63         views::BoxLayout::Orientation::kHorizontal));
64     root->AddChildView(container_);
65     SetWidget(CreateWidgetWithContent(root));
66   }
67 
68   int tap_count_ = 0;
69   int remove_show_warning_count_ = 0;
70   int remove_count_ = 0;
71 
72   views::View* container_ = nullptr;  // Owned by test widget view hierarchy.
73 
74  private:
OnTapped()75   void OnTapped() { ++tap_count_; }
OnRemoveWarningShown()76   void OnRemoveWarningShown() { ++remove_show_warning_count_; }
OnRemove()77   void OnRemove() { ++remove_count_; }
78 
79   DISALLOW_COPY_AND_ASSIGN(LoginUserViewUnittest);
80 };
81 
82 }  // namespace
83 
84 // Verifies that the user view does not change width for short/long usernames.
TEST_F(LoginUserViewUnittest,DifferentUsernamesHaveSameWidth)85 TEST_F(LoginUserViewUnittest, DifferentUsernamesHaveSameWidth) {
86   LoginUserView* large =
87       AddUserView(LoginDisplayStyle::kLarge, false /*show_dropdown*/,
88                   false /*public_account*/);
89   LoginUserView* small =
90       AddUserView(LoginDisplayStyle::kSmall, false /*show_dropdown*/,
91                   false /*public_account*/);
92   LoginUserView* extra_small =
93       AddUserView(LoginDisplayStyle::kExtraSmall, false /*show_dropdown*/,
94                   false /*public_account*/);
95 
96   int large_width = large->size().width();
97   int small_width = small->size().width();
98   int extra_small_width = extra_small->size().width();
99   EXPECT_GT(large_width, 0);
100   EXPECT_GT(small_width, 0);
101   EXPECT_GT(extra_small_width, 0);
102 
103   for (int i = 0; i < 25; ++i) {
104     LoginUserInfo user = CreateUser("user@domain.com");
105     large->UpdateForUser(user, false /*animate*/);
106     small->UpdateForUser(user, false /*animate*/);
107     extra_small->UpdateForUser(user, false /*animate*/);
108     container_->Layout();
109 
110     EXPECT_EQ(large_width, large->size().width());
111     EXPECT_EQ(small_width, small->size().width());
112     EXPECT_EQ(extra_small_width, extra_small->size().width());
113   }
114 }
115 
116 // Verifies that the user views all have different sizes with different display
117 // styles.
TEST_F(LoginUserViewUnittest,DifferentStylesHaveDifferentSizes)118 TEST_F(LoginUserViewUnittest, DifferentStylesHaveDifferentSizes) {
119   LoginUserView* large =
120       AddUserView(LoginDisplayStyle::kLarge, false /*show_dropdown*/,
121                   false /*public_account*/);
122   LoginUserView* small =
123       AddUserView(LoginDisplayStyle::kSmall, false /*show_dropdown*/,
124                   false /*public_account*/);
125   LoginUserView* extra_small =
126       AddUserView(LoginDisplayStyle::kExtraSmall, false /*show_dropdown*/,
127                   false /*public_account*/);
128 
129   EXPECT_NE(large->size(), gfx::Size());
130   EXPECT_NE(large->size(), small->size());
131   EXPECT_NE(large->size(), extra_small->size());
132   EXPECT_NE(small->size(), extra_small->size());
133 }
134 
135 // Verifies that displaying the dropdown does not change the view size. Further,
136 // the dropdown should not change the centering for the user label.
TEST_F(LoginUserViewUnittest,DropdownDoesNotChangeSize)137 TEST_F(LoginUserViewUnittest, DropdownDoesNotChangeSize) {
138   LoginUserView* with =
139       AddUserView(LoginDisplayStyle::kLarge, true /*show_dropdown*/,
140                   false /*public_account*/);
141   LoginUserView* without =
142       AddUserView(LoginDisplayStyle::kLarge, false /*show_dropdown*/,
143                   false /*public_account*/);
144   EXPECT_NE(with->size(), gfx::Size());
145   EXPECT_EQ(with->size(), without->size());
146 
147   views::View* with_label = LoginUserView::TestApi(with).user_label();
148   views::View* without_label = LoginUserView::TestApi(without).user_label();
149 
150   EXPECT_EQ(with_label->GetBoundsInScreen().x(),
151             without_label->GetBoundsInScreen().x());
152   EXPECT_NE(with_label->size(), gfx::Size());
153   EXPECT_EQ(with_label->size(), without_label->size());
154 }
155 
156 // Verifies that the entire user view is a tap target, and not just (for
157 // example) the user icon.
TEST_F(LoginUserViewUnittest,EntireViewIsTapTarget)158 TEST_F(LoginUserViewUnittest, EntireViewIsTapTarget) {
159   LoginUserView* view =
160       AddUserView(LoginDisplayStyle::kSmall, false /*show_dropdown*/,
161                   false /*public_account*/);
162   EXPECT_NE(view->size(), gfx::Size());
163 
164   // Returns true if there is a tap at |point| offset by |dx|, |dy|.
165   auto tap = [this](gfx::Point point, int dx, int dy) -> bool {
166     point.Offset(dx, dy);
167     GetEventGenerator()->MoveMouseTo(point);
168     GetEventGenerator()->ClickLeftButton();
169     bool result = tap_count_ == 1;
170     tap_count_ = 0;
171     return result;
172   };
173 
174   // Click various locations inside of the view.
175   EXPECT_TRUE(tap(view->GetBoundsInScreen().CenterPoint(), 0, 0));
176   EXPECT_TRUE(tap(view->GetBoundsInScreen().origin(), 0, 0));
177   EXPECT_TRUE(tap(view->GetBoundsInScreen().top_right(), -1, 0));
178   EXPECT_TRUE(tap(view->GetBoundsInScreen().bottom_left(), 0, -1));
179   EXPECT_TRUE(tap(view->GetBoundsInScreen().bottom_right(), -1, -1));
180 
181   // Click a location outside of the view bounds.
182   EXPECT_FALSE(tap(view->GetBoundsInScreen().bottom_right(), 1, 1));
183 }
184 
185 // Verifies the focused user view is opaque. Verifies that a hovered view is
186 // opaque. Verifies the interaction between focus and hovered opaqueness.
TEST_F(LoginUserViewUnittest,FocusHoverOpaqueInteractions)187 TEST_F(LoginUserViewUnittest, FocusHoverOpaqueInteractions) {
188   LoginUserView* one =
189       AddUserView(LoginDisplayStyle::kSmall, false /*show_dropdown*/,
190                   false /*public_account*/);
191   LoginUserView* two =
192       AddUserView(LoginDisplayStyle::kSmall, false /*show_dropdown*/,
193                   false /*public_account*/);
194   LoginUserView::TestApi one_test(one);
195   LoginUserView::TestApi two_test(two);
196 
197   // Start out as non-opaque.
198   EXPECT_FALSE(one_test.is_opaque());
199   EXPECT_FALSE(two_test.is_opaque());
200 
201   // Only the focused element is opaque.
202   one_test.tap_button()->RequestFocus();
203   EXPECT_TRUE(one_test.is_opaque());
204   EXPECT_FALSE(two_test.is_opaque());
205   two_test.tap_button()->RequestFocus();
206   EXPECT_FALSE(one_test.is_opaque());
207   EXPECT_TRUE(two_test.is_opaque());
208 
209   // Non-focused element can be opaque if the mouse is over it.
210   GetEventGenerator()->MoveMouseTo(one->GetBoundsInScreen().CenterPoint());
211   EXPECT_TRUE(one_test.is_opaque());
212   EXPECT_TRUE(two_test.is_opaque());
213 
214   // Focused element stays opaque when mouse is over it.
215   GetEventGenerator()->MoveMouseTo(two->GetBoundsInScreen().CenterPoint());
216   EXPECT_FALSE(one_test.is_opaque());
217   EXPECT_TRUE(two_test.is_opaque());
218 
219   // Focused element stays opaque when mouse leaves it.
220   GetEventGenerator()->MoveMouseTo(one->GetBoundsInScreen().CenterPoint());
221   EXPECT_TRUE(one_test.is_opaque());
222   EXPECT_TRUE(two_test.is_opaque());
223 
224   // Losing focus (after a mouse hover) makes the element transparent.
225   one_test.tap_button()->RequestFocus();
226   EXPECT_TRUE(one_test.is_opaque());
227   EXPECT_FALSE(two_test.is_opaque());
228 }
229 
230 // Verifies that forced opaque keeps the element opaque even if it gains/loses
231 // focus, and that a forced opaque element can transition to both
232 // opaque/transparent when losing forced opaque.
TEST_F(LoginUserViewUnittest,ForcedOpaque)233 TEST_F(LoginUserViewUnittest, ForcedOpaque) {
234   LoginUserView* one =
235       AddUserView(LoginDisplayStyle::kSmall, false /*show_dropdown*/,
236                   false /*public_account*/);
237   LoginUserView* two =
238       AddUserView(LoginDisplayStyle::kSmall, false /*show_dropdown*/,
239                   false /*public_account*/);
240   LoginUserView::TestApi one_test(one);
241   LoginUserView::TestApi two_test(two);
242 
243   // Start out as non-opaque.
244   EXPECT_FALSE(one_test.is_opaque());
245   EXPECT_FALSE(two_test.is_opaque());
246 
247   // Non-opaque becomes opaque with SetForceOpaque.
248   one->SetForceOpaque(true);
249   EXPECT_TRUE(one_test.is_opaque());
250   EXPECT_FALSE(two_test.is_opaque());
251 
252   // Forced opaque stays opaque when gaining or losing focus.
253   one_test.tap_button()->RequestFocus();
254   EXPECT_TRUE(one_test.is_opaque());
255   EXPECT_FALSE(two_test.is_opaque());
256   two_test.tap_button()->RequestFocus();
257   EXPECT_TRUE(one_test.is_opaque());
258   EXPECT_TRUE(two_test.is_opaque());
259 
260   // An element can become transparent when losing forced opaque.
261   EXPECT_TRUE(two_test.tap_button()->HasFocus());
262   one->SetForceOpaque(false);
263   EXPECT_FALSE(one_test.is_opaque());
264   EXPECT_TRUE(two_test.is_opaque());
265 
266   // An element can stay opaque when losing forced opaque.
267   EXPECT_TRUE(two_test.tap_button()->HasFocus());
268   two->SetForceOpaque(true);
269   EXPECT_FALSE(one_test.is_opaque());
270   EXPECT_TRUE(two_test.is_opaque());
271   two->SetForceOpaque(false);
272   EXPECT_FALSE(one_test.is_opaque());
273   EXPECT_TRUE(two_test.is_opaque());
274 }
275 
276 // Verifies that a long user name does not push the label or dropdown button
277 // outside of the LoginUserView bounds.
TEST_F(LoginUserViewUnittest,ElideUserLabel)278 TEST_F(LoginUserViewUnittest, ElideUserLabel) {
279   LoginUserView* view =
280       AddUserView(LoginDisplayStyle::kLarge, true /*show_dropdown*/,
281                   false /*public_account*/);
282   LoginUserView::TestApi view_test(view);
283 
284   LoginUserInfo user = CreateUser("verylongusernamethatfillsthebox@domain.com");
285   view->UpdateForUser(user, false /*animate*/);
286   container_->Layout();
287 
288   EXPECT_TRUE(view->GetVisibleBounds().Contains(
289       view_test.user_label()->GetVisibleBounds()));
290   EXPECT_TRUE(view->GetVisibleBounds().Contains(
291       view_test.dropdown()->GetVisibleBounds()));
292 }
293 
294 // Verifies that displaying the domain does not change the view width.
295 // Also domain should have the same horizontal centering as user label.
TEST_F(LoginUserViewUnittest,DomainDoesNotChangeWidth)296 TEST_F(LoginUserViewUnittest, DomainDoesNotChangeWidth) {
297   LoginUserView* public_account =
298       AddUserView(LoginDisplayStyle::kLarge, false /*show_dropdown*/,
299                   true /*public_account*/);
300   LoginUserView* regular_user =
301       AddUserView(LoginDisplayStyle::kLarge, false /*show_dropdown*/,
302                   false /*public_account*/);
303   EXPECT_NE(regular_user->size().width(), 0);
304   EXPECT_EQ(regular_user->size().width(), public_account->size().width());
305 
306   views::View* user_label = LoginUserView::TestApi(public_account).user_label();
307   views::View* user_domain =
308       LoginUserView::TestApi(public_account).user_label();
309   EXPECT_EQ(user_label->GetBoundsInScreen().CenterPoint().x(),
310             user_domain->GetBoundsInScreen().CenterPoint().x());
311 }
312 
313 }  // namespace ash
314