1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
ash / shelf / launcher_nudge_controller_unittest.cc [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/shelf/launcher_nudge_controller.h"
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/app_list/test/app_list_test_helper.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shelf/home_button.h"
#include "ash/shelf/scrollable_shelf_view.h"
#include "ash/shelf/shelf_controller.h"
#include "ash/shelf/shelf_test_util.h"
#include "ash/shelf/shelf_view_test_api.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "base/command_line.h"
#include "base/json/values_util.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
namespace ash {
class TestNudgeAnimationObserver : public HomeButton::NudgeAnimationObserver {
public:
explicit TestNudgeAnimationObserver(HomeButton* home_button)
: home_button_(home_button) {
home_button_->AddNudgeAnimationObserverForTest(this);
}
~TestNudgeAnimationObserver() override {
home_button_->RemoveNudgeAnimationObserverForTest(this);
}
// HomeButton::NudgeAnimationObserver:
void NudgeAnimationStarted(HomeButton* home_button) override {
if (home_button != home_button_)
return;
++started_animation_count_;
}
void NudgeAnimationEnded(HomeButton* home_button) override {
if (home_button != home_button_)
return;
DCHECK_EQ(started_animation_count_, ended_animation_count_ + 1);
++ended_animation_count_;
animation_run_loop_.Quit();
}
void NudgeLabelShown(HomeButton* home_button) override {
if (home_button != home_button_)
return;
label_run_loop_.Quit();
}
void WaitUntilLabelShown() {
ASSERT_TRUE(home_button_->CanShowNudgeLabel());
DCHECK_GE(started_animation_count_, ended_animation_count_);
if (started_animation_count_ == ended_animation_count_)
return;
// Block the test to wait until the label is shown.
label_run_loop_.Run();
}
void WaitUntilAnimationEnded() {
DCHECK_GE(started_animation_count_, ended_animation_count_);
if (started_animation_count_ == ended_animation_count_)
return;
// Block the test to wait until the animation ended.
animation_run_loop_.Run();
}
// Returns the number of finished animation on this home_button_.
int GetShownCount() const { return ended_animation_count_; }
private:
base::RunLoop animation_run_loop_;
base::RunLoop label_run_loop_;
const raw_ptr<HomeButton> home_button_;
// Counts the number of started/ended animations.
int started_animation_count_ = 0;
int ended_animation_count_ = 0;
};
class LauncherNudgeControllerTest : public AshTestBase {
public:
LauncherNudgeControllerTest()
: AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
LauncherNudgeControllerTest(const LauncherNudgeControllerTest&) = delete;
LauncherNudgeControllerTest& operator=(const LauncherNudgeControllerTest&) =
delete;
~LauncherNudgeControllerTest() override = default;
void SetUp() override {
AshTestBase::SetUp();
nudge_controller_ =
Shell::Get()->shelf_controller()->launcher_nudge_controller();
nudge_controller_->SetClockForTesting(
task_environment()->GetMockClock(),
task_environment()->GetMockTickClock());
scrollable_shelf_view_ = GetPrimaryShelf()
->shelf_widget()
->hotseat_widget()
->scrollable_shelf_view();
test_api_ = std::make_unique<ShelfViewTestAPI>(
scrollable_shelf_view_->shelf_view());
}
// Advances the mock clock in the task environment and wait until it is idle.
// Note that AdvanceClock is used here instead of FastForwardBy because
// `delay` used in test cases are too long for FastForwardBy to process and
// will cause timeout running the tests.
void AdvanceClock(base::TimeDelta delay) {
task_environment()->AdvanceClock(delay);
task_environment()->RunUntilIdle();
}
int GetNudgeShownCount() {
PrefService* pref_service =
Shell::Get()->session_controller()->GetLastActiveUserPrefService();
return LauncherNudgeController::GetShownCount(pref_service);
}
void AddAppShortcut(int& id) {
ShelfItem item = ShelfTestUtil::AddAppShortcut(base::NumberToString(id++),
TYPE_PINNED_APP);
// Wait for shelf view's bounds animation to end. Otherwise the scrollable
// shelf's bounds are not updated yet.
test_api_->RunMessageLoopUntilAnimationsDone();
}
raw_ptr<LauncherNudgeController, DanglingUntriaged> nudge_controller_;
std::unique_ptr<TestNudgeAnimationObserver> observer_;
raw_ptr<ScrollableShelfView, DanglingUntriaged> scrollable_shelf_view_ =
nullptr;
std::unique_ptr<ShelfViewTestAPI> test_api_;
};
TEST_F(LauncherNudgeControllerTest, DisableNudgeForGuestSession) {
// Simulate a guest user logging in, where the session is ephemeral.
SimulateGuestLogin();
// Do not show the nudge in the guest session.
EXPECT_FALSE(nudge_controller_->IsRecheckTimerRunningForTesting());
EXPECT_EQ(0, GetNudgeShownCount());
}
TEST_F(LauncherNudgeControllerTest, NoNudgeWhenSkippedByCommandLineFlag) {
// Unit tests run with a scoped command line, so directly set the flag.
base::CommandLine::ForCurrentProcess()->AppendSwitch(switches::kAshNoNudges);
SimulateUserLogin("user@gmail.com");
EXPECT_FALSE(nudge_controller_->IsRecheckTimerRunningForTesting());
EXPECT_EQ(0, GetNudgeShownCount());
}
TEST_F(LauncherNudgeControllerTest, DisableNudgeForExistingUser) {
// Simulate a existing user logging in.
SimulateUserLogin("user@gmail.com");
ASSERT_FALSE(Shell::Get()->session_controller()->IsUserFirstLogin());
// Do not show the nudge to an existing user.
EXPECT_FALSE(nudge_controller_->IsRecheckTimerRunningForTesting());
EXPECT_EQ(0, GetNudgeShownCount());
}
TEST_F(LauncherNudgeControllerTest, BasicTest) {
// Set the animation duration mode to non-zero for the launcher nudge
// animation to actually run in the tests.
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
SimulateNewUserFirstLogin("user@gmail.com");
ASSERT_TRUE(Shell::Get()->session_controller()->IsUserFirstLogin());
EXPECT_EQ(0, GetNudgeShownCount());
for (int i = 0; i < LauncherNudgeController::kMaxShownCount; ++i) {
// After the new user logged in or the nudge was just shown, the recheck
// timer should be running to wait for a certain amount of time to check if
// the nudge should be shown later.
EXPECT_TRUE(nudge_controller_->IsRecheckTimerRunningForTesting());
if (i == 0) {
// The first waiting interval is set different from the following ones.
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/true));
} else {
AdvanceClock(
nudge_controller_->GetNudgeInterval(/*is_first_time=*/false));
}
// The nudge animation should be shown and the shown count should be
// incremented.
EXPECT_EQ(i + 1, GetNudgeShownCount());
}
// After showing `kMaxShownCount` times of the nudge animation, stop showing
// nudges to the users.
EXPECT_FALSE(nudge_controller_->IsRecheckTimerRunningForTesting());
}
TEST_F(LauncherNudgeControllerTest, StopShowingNudgeAfterLauncherIsOpened) {
// Set the animation duration mode to non-zero for the launcher nudge
// animation to actually run in the tests.
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
SimulateNewUserFirstLogin("user@gmail.com");
EXPECT_EQ(0, GetNudgeShownCount());
EXPECT_TRUE(nudge_controller_->IsRecheckTimerRunningForTesting());
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/true));
EXPECT_EQ(1, GetNudgeShownCount());
// Toggle the app list to show.
Shell::Get()->app_list_controller()->ToggleAppList(
display::Screen::GetScreen()->GetPrimaryDisplay().id(),
AppListShowSource::kShelfButton, base::TimeTicks());
ASSERT_TRUE(Shell::Get()->app_list_controller()->IsVisible());
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/false));
// If the launcher app list has been opened, stop showing the nudge to the
// user.
EXPECT_FALSE(nudge_controller_->IsRecheckTimerRunningForTesting());
EXPECT_EQ(1, GetNudgeShownCount());
}
TEST_F(LauncherNudgeControllerTest, DoNotShowNudgeInTabletMode) {
// Set the animation duration mode to non-zero for the launcher nudge
// animation to actually run in the tests.
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
SimulateNewUserFirstLogin("user@gmail.com");
EXPECT_EQ(0, GetNudgeShownCount());
// Change to the tablet mode. Do not show the nudge in tablet mode.
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/true));
EXPECT_EQ(0, GetNudgeShownCount());
// Return to the clamshell mode.
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false);
EXPECT_EQ(0, GetNudgeShownCount());
// The nudge has to wait for `kMinIntervalAfterHomeButtonAppears` after
// changing back to clamshell mode.
AdvanceClock(LauncherNudgeController::kMinIntervalAfterHomeButtonAppears);
EXPECT_EQ(1, GetNudgeShownCount());
}
TEST_F(LauncherNudgeControllerTest, ShowNudgeOnDisplayWhereCursorIsOn) {
// Set the animation duration mode to non-zero for the launcher nudge
// animation to actually run in the tests.
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
SimulateNewUserFirstLogin("user@gmail.com");
EXPECT_EQ(0, GetNudgeShownCount());
// Create 2 displays for the test.
UpdateDisplay("800x600,800x600");
ASSERT_EQ(2u, Shell::Get()->display_manager()->GetNumDisplays());
HomeButton* primary_home_button =
LauncherNudgeController::GetHomeButtonForDisplay(
GetPrimaryDisplay().id());
HomeButton* secondary_home_button =
LauncherNudgeController::GetHomeButtonForDisplay(
GetSecondaryDisplay().id());
TestNudgeAnimationObserver waiter_primary(primary_home_button);
TestNudgeAnimationObserver waiter_secondary(secondary_home_button);
// Move the cursor to primary display. The nudge after advancing the clock
// should be shown on the primary display.
Shell::Get()->cursor_manager()->SetDisplay(GetPrimaryDisplay());
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/true));
// Wait until the animation ends to count the finished animation.
waiter_primary.WaitUntilAnimationEnded();
EXPECT_EQ(1, GetNudgeShownCount());
EXPECT_EQ(1, waiter_primary.GetShownCount());
EXPECT_EQ(0, waiter_secondary.GetShownCount());
// Move the cursor to primary display. The nudge after advancing the clock
// should be shown on the primary display.
Shell::Get()->cursor_manager()->SetDisplay(GetSecondaryDisplay());
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/false));
// Wait until the animation ends to count the finished animation.
waiter_secondary.WaitUntilAnimationEnded();
EXPECT_EQ(2, GetNudgeShownCount());
EXPECT_EQ(1, waiter_primary.GetShownCount());
EXPECT_EQ(1, waiter_secondary.GetShownCount());
}
TEST_F(LauncherNudgeControllerTest,
WaitUntilHomeButtonStaysLongEnoughToShowNudge) {
// Set the animation duration mode to non-zero for the launcher nudge
// animation to actually run in the tests.
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
// New user logs in.
SimulateNewUserFirstLogin("user@gmail.com");
EXPECT_EQ(0, GetNudgeShownCount());
base::TimeDelta small_delta = base::Seconds(10);
// Log out right before the nudge should be shown.
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/true) -
small_delta);
ClearLogin();
// Log in again.
SimulateUserLogin("user@gmail.com");
AdvanceClock(small_delta);
// Even if the nudge interval has passed since the first log in, the nudge has
// to wait `kMinIntervalAfterHomeButtonAppears` amount of time since the
// recent login to be shown.
EXPECT_EQ(0, GetNudgeShownCount());
AdvanceClock(LauncherNudgeController::kMinIntervalAfterHomeButtonAppears -
small_delta);
EXPECT_EQ(1, GetNudgeShownCount());
// Change to the tablet mode.
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/true) -
small_delta);
// Return to the clamshell mode.
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false);
AdvanceClock(small_delta);
// Even if the nudge interval has passed since the last nudge shown, the nudge
// has to wait `kMinIntervalAfterHomeButtonAppears` amount of time since the
// last change to clamshell mode to be shown.
EXPECT_EQ(1, GetNudgeShownCount());
AdvanceClock(LauncherNudgeController::kMinIntervalAfterHomeButtonAppears -
small_delta);
EXPECT_EQ(2, GetNudgeShownCount());
}
TEST_F(LauncherNudgeControllerTest, NudgeLabelVisibilityTest) {
// Set the animation duration mode to non-zero for the launcher nudge
// animation to actually run in the tests.
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
SimulateNewUserFirstLogin("user@gmail.com");
EXPECT_EQ(GetNudgeShownCount(), 0);
HomeButton* home_button = LauncherNudgeController::GetHomeButtonForDisplay(
GetPrimaryDisplay().id());
TestNudgeAnimationObserver waiter(home_button);
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/true));
// Wait until the label to be shown and check if the label is visible.
waiter.WaitUntilLabelShown();
views::View* label_container = home_button->expandable_container_for_test();
EXPECT_TRUE(label_container && label_container->GetVisible());
EXPECT_EQ(label_container->layer()->opacity(), 1);
// Wait until the label is hidden.
AdvanceClock(base::TimeDelta(base::Seconds(6)));
waiter.WaitUntilAnimationEnded();
EXPECT_FALSE(label_container->GetVisible());
EXPECT_EQ(label_container->layer()->opacity(), 0);
EXPECT_EQ(GetNudgeShownCount(), 1);
TestNudgeAnimationObserver waiter2(home_button);
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/false) -
base::Seconds(6));
waiter2.WaitUntilLabelShown();
EXPECT_TRUE(label_container->GetVisible());
gfx::Point center = label_container->GetBoundsInScreen().CenterPoint();
GetEventGenerator()->MoveMouseTo(center);
// Click on the nudge label should toggle the app list.
GetEventGenerator()->ClickLeftButton();
GetAppListTestHelper()->WaitUntilIdle();
GetAppListTestHelper()->CheckVisibility(true);
// Clicking on the nudge label should animate it. Wait until the animation
// ends.
waiter2.WaitUntilAnimationEnded();
// The label is removed after it is clicked.
EXPECT_FALSE(home_button->expandable_container_for_test());
}
TEST_F(LauncherNudgeControllerTest, AnimationUsedDependsOnAvailableSpace) {
// Set the animation duration mode to non-zero for the launcher nudge
// animation to actually run in the tests.
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
SimulateNewUserFirstLogin("user@gmail.com");
EXPECT_EQ(GetNudgeShownCount(), 0);
HomeButton* home_button = LauncherNudgeController::GetHomeButtonForDisplay(
GetPrimaryDisplay().id());
// Advance the clock to trigger the animation and create the label nudge.
AdvanceClock(nudge_controller_->GetNudgeInterval(/*is_first_time=*/true));
// Without adding anything to shelf, there should be enough space to show
// nudge label.
EXPECT_TRUE(home_button->CanShowNudgeLabel());
int id = 0;
// Add app shortcuts until the hotseat overflow.
while (scrollable_shelf_view_->layout_strategy_for_test() ==
ScrollableShelfView::kNotShowArrowButtons) {
AddAppShortcut(id);
}
// If the apps overflow in shelf, there should be no space for the label to be
// shown.
EXPECT_FALSE(home_button->CanShowNudgeLabel());
}
} // namespace ash