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
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
ash / public / cpp / app_list / app_list_config_provider_unittest.cc [blame]
// Copyright 2019 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/public/cpp/app_list/app_list_config_provider.h"
#include <string>
#include <vector>
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/app_list/app_list_config.h"
#include "ash/public/cpp/app_list/app_list_features.h"
#include "base/containers/contains.h"
#include "base/ranges/algorithm.h"
#include "base/test/scoped_feature_list.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace ash {
namespace {
// The expected number of columns in the tablet app list apps grid.
constexpr int kPreferredGridColumnsForWorkArea = 5;
// Does sanity check on apps grid item tile dimensions in config. On error, it
// causes test failure with additional |scoped_trace| message.
// |error_margin|: Allowed error margin when comparing vertical dimensions. Used
// for scaled app list config (to account for possible rounding
// errors).
void SanityCheckGridTileDimensions(AppListConfig* config, int error_margin) {
const int icon_top =
(config->grid_tile_height() - config->grid_icon_bottom_padding() -
config->grid_icon_dimension()) /
2;
// The app list item icon top should be within the tile bounds.
EXPECT_GE(icon_top, 0);
const int icon_bottom = icon_top + config->grid_icon_dimension();
const int title_top = config->grid_tile_height() -
config->grid_title_bottom_padding() -
config->app_title_font().GetHeight();
// Icon should not overlap with title.
EXPECT_LE(icon_bottom, title_top + error_margin);
// Icon should fit within available height.
EXPECT_LE(icon_bottom,
config->grid_tile_height() - config->grid_icon_bottom_padding());
// Icon should fit in the tile width.
EXPECT_LE(config->grid_icon_dimension(), config->grid_tile_width());
const int folder_icon_top =
(config->grid_tile_height() - config->grid_icon_bottom_padding() -
config->folder_icon_dimension()) /
2;
// The app list folder icon top should be within the tile bounds.
EXPECT_GE(folder_icon_top, 0);
// Unclipped folder icon should not overlap with title.
const int folder_icon_bottom =
folder_icon_top + config->folder_icon_dimension();
EXPECT_LE(folder_icon_bottom, title_top + error_margin);
// Unclipped folder icon should fit within available height.
EXPECT_LE(folder_icon_bottom,
config->grid_tile_height() - config->grid_icon_bottom_padding());
// Unclipped folder icon should fit into tile width.
EXPECT_LE(config->folder_icon_dimension(), config->grid_tile_width());
}
class TestAppListConfigProviderObserver
: public AppListConfigProvider::Observer {
public:
TestAppListConfigProviderObserver() = default;
TestAppListConfigProviderObserver(const TestAppListConfigProviderObserver&) =
delete;
TestAppListConfigProviderObserver& operator=(
const TestAppListConfigProviderObserver&) = delete;
~TestAppListConfigProviderObserver() override = default;
// AppListConfigProvider::Observer:
void OnAppListConfigCreated(ash::AppListConfigType config_type) override {
ASSERT_FALSE(base::Contains(created_types_, config_type));
created_types_.push_back(config_type);
}
const std::vector<ash::AppListConfigType>& created_types() const {
return created_types_;
}
void ClearCreatedTypes() { created_types_.clear(); }
private:
std::vector<ash::AppListConfigType> created_types_;
};
} // namespace
class AppListConfigProviderTest : public testing::Test {
public:
AppListConfigProviderTest() = default;
~AppListConfigProviderTest() override = default;
void SetUp() override {
// AppListConfigProvider is base::NoDestruct, which means it may not be
// cleared between test runs - clear the registry to ensure this test starts
// with the clean app list config registry.
AppListConfigProvider::Get().ResetForTesting();
AppListConfigProvider::Get().AddObserver(®istry_observer_);
}
void TearDown() override {
AppListConfigProvider::Get().RemoveObserver(®istry_observer_);
AppListConfigProvider::Get().ResetForTesting();
}
void VerifyScaledConfig(const AppListConfig& base_config,
AppListConfig* config,
float scale_x) {
ASSERT_TRUE(config);
EXPECT_EQ(base_config.type(), config->type());
EXPECT_EQ(scale_x, config->scale_x());
EXPECT_EQ(std::round(base_config.grid_tile_width() * scale_x),
config->grid_tile_width());
EXPECT_EQ(base_config.grid_tile_height(), config->grid_tile_height());
auto get_grid_title_height = [](const AppListConfig* config) {
return config->grid_tile_height() - config->grid_title_top_padding() -
config->grid_title_bottom_padding();
};
EXPECT_EQ(get_grid_title_height(&base_config),
get_grid_title_height(config));
SanityCheckGridTileDimensions(config, 1);
}
protected:
TestAppListConfigProviderObserver registry_observer_;
};
// Tests GetConfigForType behavior.
TEST_F(AppListConfigProviderTest, ConfigGetters) {
std::vector<AppListConfigType> test_cases = {AppListConfigType::kRegular,
AppListConfigType::kDense};
std::set<AppListConfigType> created_types;
for (const auto& config_type : test_cases) {
SCOPED_TRACE(static_cast<int>(config_type));
// Calling GetConfigForType with false |can_create| will not create a new
// config.
EXPECT_FALSE(AppListConfigProvider::Get().GetConfigForType(
config_type, false /*can_create*/));
EXPECT_EQ(std::vector<AppListConfigType>(),
registry_observer_.created_types());
// Calling GetConfigForType with true |can_create| will create a new config
// (if not previously created), and it will notify observers a config was
// created.
const AppListConfig* config = AppListConfigProvider::Get().GetConfigForType(
config_type, true /*can_create*/);
ASSERT_TRUE(config);
created_types.insert(config_type);
EXPECT_EQ(config_type, config->type());
const std::vector<AppListConfigType> expected_created_types = {config_type};
EXPECT_EQ(expected_created_types, registry_observer_.created_types());
// Subsequent calls to GetConfigForType will return previously created
// config, and will not notify observers of config creation.
EXPECT_EQ(config, AppListConfigProvider::Get().GetConfigForType(config_type,
false));
EXPECT_EQ(config,
AppListConfigProvider::Get().GetConfigForType(config_type, true));
EXPECT_EQ(expected_created_types, registry_observer_.created_types());
EXPECT_EQ(created_types,
AppListConfigProvider::Get().GetAvailableConfigTypes());
registry_observer_.ClearCreatedTypes();
}
}
// Tests calling CreateForFullscreenAppList creates the appropriate app list
// configuration depending on display size.
TEST_F(AppListConfigProviderTest, CreateConfigByDisplayWorkArea) {
// NOTE: The `available_size` are arbitrary values large enough so the
// returned app list config does not get scaled down (i.e. large enough so
// they can fit an apps grid with default sized-items).
const struct TestCase {
gfx::Size work_area_size;
gfx::Size available_size;
AppListConfigType config_type;
} test_cases[] = {
{gfx::Size(900, 500), gfx::Size(788, 321), AppListConfigType::kDense},
{gfx::Size(540, 900), gfx::Size(428, 704), AppListConfigType::kDense},
{gfx::Size(960, 600), gfx::Size(848, 412), AppListConfigType::kDense},
{gfx::Size(1100, 700), gfx::Size(988, 504), AppListConfigType::kRegular},
{gfx::Size(600, 960), gfx::Size(488, 764), AppListConfigType::kDense},
{gfx::Size(700, 1100), gfx::Size(588, 904), AppListConfigType::kRegular},
{gfx::Size(1200, 768), gfx::Size(1088, 572), AppListConfigType::kRegular},
{gfx::Size(768, 1200), gfx::Size(656, 1004),
AppListConfigType::kRegular}};
for (const auto& test_case : test_cases) {
SCOPED_TRACE(::testing::Message()
<< "Size: " << test_case.work_area_size.ToString()
<< ", expected config type: "
<< static_cast<int>(test_case.config_type));
std::unique_ptr<AppListConfig> config =
AppListConfigProvider::Get().CreateForTabletAppList(
test_case.work_area_size, kPreferredGridColumnsForWorkArea,
test_case.available_size, nullptr);
ASSERT_TRUE(config.get());
EXPECT_EQ(test_case.config_type, config->type());
EXPECT_EQ(1, config->scale_x());
SanityCheckGridTileDimensions(config.get(), 0);
// Verify that AppListConfigProvider now provides the created config type.
EXPECT_TRUE(AppListConfigProvider::Get().GetConfigForType(
test_case.config_type, false));
// NOTE: While a specific config might be expected in more than one test
// case, it should only get reported as created once - given that the
// observed created types are not cleared for |registry_observer_| between
// test cases, the "observed" count for |test_case.config_type| should
// always be 1.
EXPECT_EQ(1, base::ranges::count(registry_observer_.created_types(),
test_case.config_type));
// Verify CreateForAppListWidget returns nullptr if the created config would
// be the same as |config|.
EXPECT_FALSE(AppListConfigProvider::Get().CreateForTabletAppList(
test_case.work_area_size, kPreferredGridColumnsForWorkArea,
test_case.available_size, config.get()));
}
}
// Tests whether CreateForAppListWidget returns a new config depending on the
// value of the old config passed to the method.
TEST_F(AppListConfigProviderTest,
CreateConfigByDisplayWorkAreaWithNonNullConfig) {
// Create initial configuration.
gfx::Size work_area(1200, 768);
gfx::Size available_size(1088, 572);
std::unique_ptr<AppListConfig> config =
AppListConfigProvider::Get().CreateForTabletAppList(
work_area, kPreferredGridColumnsForWorkArea, available_size, nullptr);
ASSERT_TRUE(config);
EXPECT_EQ(AppListConfigType::kRegular, config->type());
// Verify CreateForAppListWidget returns nullptr if the created config would
// be the same as `config`.
work_area = gfx::Size(768, 1200);
available_size = gfx::Size(656, 1004);
EXPECT_FALSE(AppListConfigProvider::Get().CreateForTabletAppList(
work_area, kPreferredGridColumnsForWorkArea, available_size,
config.get()));
// Create different config.
work_area = gfx::Size(960, 600);
available_size = gfx::Size(848, 412);
std::unique_ptr<AppListConfig> updated_config =
AppListConfigProvider::Get().CreateForTabletAppList(
work_area, kPreferredGridColumnsForWorkArea, available_size,
config.get());
ASSERT_TRUE(updated_config);
EXPECT_EQ(AppListConfigType::kDense, updated_config->type());
}
TEST_F(AppListConfigProviderTest,
CreateScaledConfigByDisplayWorkAreaRegularLandscape) {
// The available grid size fits the grid - created config is not scaled.
const gfx::Size work_area(1200, 768);
const gfx::Size initial_available_size(1088, 572);
std::unique_ptr<AppListConfig> base_config =
AppListConfigProvider::Get().CreateForTabletAppList(
work_area, kPreferredGridColumnsForWorkArea, initial_available_size,
nullptr);
ASSERT_TRUE(base_config.get());
ASSERT_EQ(AppListConfigType::kRegular, base_config->type());
ASSERT_EQ(1, base_config->scale_x());
const int kMinGridWidth =
base_config->grid_tile_width() * kPreferredGridColumnsForWorkArea;
{
SCOPED_TRACE("Horizontal scaling");
// Reduce available width so the grid scales down horizontally.
const gfx::Size available_size(480, initial_available_size.height());
std::unique_ptr<AppListConfig> config =
AppListConfigProvider::Get().CreateForTabletAppList(
work_area, kPreferredGridColumnsForWorkArea, available_size,
nullptr);
VerifyScaledConfig(*base_config, config.get(), 480.0f / kMinGridWidth);
}
{
SCOPED_TRACE("Vertical scaling");
// Reduce available height so the grid doesn't fit `preferred_rows` - the
// config should not be scaled, as apps grid is expected to reduce the
// number of visible rows in this case.
const gfx::Size available_size(initial_available_size.width(), 400);
std::unique_ptr<AppListConfig> config =
AppListConfigProvider::Get().CreateForTabletAppList(
work_area, kPreferredGridColumnsForWorkArea, available_size,
nullptr);
VerifyScaledConfig(*base_config, config.get(), 1);
}
{
SCOPED_TRACE("Horizontal and vertical scaling");
// Reduce both available width and height, and expect the grid to scale down
// horizontally only.
const gfx::Size available_size(480, 400);
std::unique_ptr<AppListConfig> config =
AppListConfigProvider::Get().CreateForTabletAppList(
work_area, kPreferredGridColumnsForWorkArea, available_size,
nullptr);
VerifyScaledConfig(*base_config, config.get(), 480.0f / kMinGridWidth);
}
}
TEST_F(AppListConfigProviderTest,
CreateScaledConfigByDisplayWorkAreaDenseLandscape) {
// The available grid size fits the grid - created config is not scaled.
const gfx::Size work_area(960, 600);
const gfx::Size initial_available_size(848, 412);
std::unique_ptr<AppListConfig> base_config =
AppListConfigProvider::Get().CreateForTabletAppList(
work_area, kPreferredGridColumnsForWorkArea, initial_available_size,
nullptr);
ASSERT_TRUE(base_config.get());
ASSERT_EQ(AppListConfigType::kDense, base_config->type());
ASSERT_EQ(1, base_config->scale_x());
const int kMinGridWidth =
base_config->grid_tile_width() * kPreferredGridColumnsForWorkArea;
{
SCOPED_TRACE("Horizontal scaling");
// Reduce available width so the grid scales down horizontally.
const gfx::Size available_size(300, initial_available_size.height());
std::unique_ptr<AppListConfig> config =
AppListConfigProvider::Get().CreateForTabletAppList(
work_area, kPreferredGridColumnsForWorkArea, available_size,
nullptr);
VerifyScaledConfig(*base_config, config.get(), 300.0f / kMinGridWidth);
}
{
SCOPED_TRACE("Vertical scaling");
// Reduce available height so the grid doesn't fit `preferred_rows` - the
// config should not be scaled, as apps grid is expected to reduce the
// number of visible rows in this case.
const gfx::Size available_size(initial_available_size.width(), 200);
std::unique_ptr<AppListConfig> config =
AppListConfigProvider::Get().CreateForTabletAppList(
work_area, kPreferredGridColumnsForWorkArea, available_size,
nullptr);
VerifyScaledConfig(*base_config, config.get(), 1);
}
{
SCOPED_TRACE("Horizontal and vertical scaling");
// Reduce both available width and height, and expect the grid to scale down
// horizontally only.
const gfx::Size available_size(300, 200);
std::unique_ptr<AppListConfig> config =
AppListConfigProvider::Get().CreateForTabletAppList(
work_area, kPreferredGridColumnsForWorkArea, available_size,
nullptr);
VerifyScaledConfig(*base_config, config.get(), 300.0f / kMinGridWidth);
}
}
TEST_F(AppListConfigProviderTest,
CreateScaledConfigByDisplayWorkAreaRegularPortrait) {
// The available grid size fits the grid - created config is not scaled.
const gfx::Size work_area(768, 1200);
const gfx::Size initial_available_size(656, 1004);
std::unique_ptr<AppListConfig> base_config =
AppListConfigProvider::Get().CreateForTabletAppList(
work_area, kPreferredGridColumnsForWorkArea, initial_available_size,
nullptr);
ASSERT_TRUE(base_config.get());
ASSERT_EQ(AppListConfigType::kRegular, base_config->type());
ASSERT_EQ(1, base_config->scale_x());
const int kMinGridWidth =
base_config->grid_tile_width() * kPreferredGridColumnsForWorkArea;
{
SCOPED_TRACE("Horizontal scaling");
// Reduce available width so the grid scales down horizontally.
const gfx::Size available_size(440, initial_available_size.height());
std::unique_ptr<AppListConfig> config =
AppListConfigProvider::Get().CreateForTabletAppList(
work_area, kPreferredGridColumnsForWorkArea, available_size,
nullptr);
VerifyScaledConfig(*base_config, config.get(), 440.0f / kMinGridWidth);
}
{
SCOPED_TRACE("Vertical scaling");
// Reduce available height so the grid doesn't fit `preferred_rows` - the
// config should not be scaled, as apps grid is expected to reduce the
// number of visible rows in this case.
const gfx::Size available_size(initial_available_size.width(), 532);
std::unique_ptr<AppListConfig> config =
AppListConfigProvider::Get().CreateForTabletAppList(
work_area, kPreferredGridColumnsForWorkArea, available_size,
nullptr);
VerifyScaledConfig(*base_config, config.get(), 1);
}
{
SCOPED_TRACE("Horizontal and vertical scaling");
// Reduce both available width and height, and expect the grid to scale down
// horizontally only.
const gfx::Size available_size(440, 532);
std::unique_ptr<AppListConfig> config =
AppListConfigProvider::Get().CreateForTabletAppList(
work_area, kPreferredGridColumnsForWorkArea, available_size,
nullptr);
VerifyScaledConfig(*base_config, config.get(), 440.0f / kMinGridWidth);
}
}
TEST_F(AppListConfigProviderTest,
CreateScaledConfigByDisplayWorkAreaDensePortrait) {
// The available grid size fits the grid - created config is not scaled.
const gfx::Size work_area(600, 960);
const gfx::Size initial_available_size(488, 764);
std::unique_ptr<AppListConfig> base_config =
AppListConfigProvider::Get().CreateForTabletAppList(
work_area, kPreferredGridColumnsForWorkArea, initial_available_size,
nullptr);
ASSERT_TRUE(base_config.get());
ASSERT_EQ(AppListConfigType::kDense, base_config->type());
ASSERT_EQ(1, base_config->scale_x());
const int kMinGridWidth =
base_config->grid_tile_width() * kPreferredGridColumnsForWorkArea;
{
SCOPED_TRACE("Horizontal scaling");
// Reduce available width so the grid scales down horizontally.
const gfx::Size available_size(300, initial_available_size.height());
std::unique_ptr<AppListConfig> config =
AppListConfigProvider::Get().CreateForTabletAppList(
work_area, kPreferredGridColumnsForWorkArea, available_size,
nullptr);
VerifyScaledConfig(*base_config, config.get(), 300.0f / kMinGridWidth);
}
{
SCOPED_TRACE("Vertical scaling");
// Reduce available height so the grid doesn't fit `preferred_rows` - the
// config should not be scaled, as apps grid is expected to reduce the
// number of visible rows in this case.
const gfx::Size available_size(initial_available_size.width(), 360);
std::unique_ptr<AppListConfig> config =
AppListConfigProvider::Get().CreateForTabletAppList(
work_area, kPreferredGridColumnsForWorkArea, available_size,
nullptr);
VerifyScaledConfig(*base_config, config.get(), 1);
}
{
SCOPED_TRACE("Horizontal and vertical scaling");
// Reduce both available width and height, and expect the grid to scale down
// horizontally only.
const gfx::Size available_size(300, 320);
std::unique_ptr<AppListConfig> config =
AppListConfigProvider::Get().CreateForTabletAppList(
work_area, kPreferredGridColumnsForWorkArea, available_size,
nullptr);
VerifyScaledConfig(*base_config, config.get(), 300.0f / kMinGridWidth);
}
}
} // namespace ash