1 // Copyright (c) 2012 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.
5 #include "chrome/browser/ui/app_list/extension_app_model_builder.h"
9 #include "base/files/file_path.h"
10 #include "base/memory/scoped_ptr.h"
11 #include "base/prefs/pref_service.h"
12 #include "base/run_loop.h"
13 #include "base/values.h"
14 #include "chrome/browser/extensions/extension_function_test_utils.h"
15 #include "chrome/browser/extensions/extension_service_unittest.h"
16 #include "chrome/browser/extensions/install_tracker.h"
17 #include "chrome/browser/extensions/install_tracker_factory.h"
18 #include "chrome/browser/ui/app_list/app_list_controller_delegate_impl.h"
19 #include "chrome/common/chrome_constants.h"
20 #include "chrome/common/extensions/extension_constants.h"
21 #include "chrome/common/pref_names.h"
22 #include "chrome/test/base/testing_profile.h"
23 #include "extensions/browser/app_sorting.h"
24 #include "extensions/common/extension_set.h"
25 #include "extensions/common/manifest.h"
26 #include "testing/gtest/include/gtest/gtest.h"
27 #include "ui/app_list/app_list_item.h"
31 const char kHostedAppId[] = "dceacbkfkmllgmjmbhgkpjegnodmildf";
32 const char kPackagedApp1Id[] = "emfkafnhnpcmabnnkckkchdilgeoekbo";
33 const char kPackagedApp2Id[] = "jlklkagmeajbjiobondfhiekepofmljl";
35 // Get a string of all apps in |model| joined with ','.
36 std::string GetModelContent(app_list::AppListModel* model) {
38 for (size_t i = 0; i < model->item_list()->item_count(); ++i) {
41 content += model->item_list()->item_at(i)->title();
46 scoped_refptr<extensions::Extension> MakeApp(const std::string& name,
47 const std::string& version,
48 const std::string& url,
49 const std::string& id) {
51 base::DictionaryValue value;
52 value.SetString("name", name);
53 value.SetString("version", version);
54 value.SetString("app.launch.web_url", url);
55 scoped_refptr<extensions::Extension> app =
56 extensions::Extension::Create(
58 extensions::Manifest::INTERNAL,
60 extensions::Extension::WAS_INSTALLED_BY_DEFAULT,
67 class TestAppListControllerDelegate : public AppListControllerDelegate {
69 virtual ~TestAppListControllerDelegate() {}
70 virtual void DismissView() OVERRIDE {}
71 virtual gfx::NativeWindow GetAppListWindow() OVERRIDE { return NULL; }
72 virtual gfx::ImageSkia GetWindowIcon() OVERRIDE { return gfx::ImageSkia(); }
73 virtual bool IsAppPinned(const std::string& extension_id) OVERRIDE {
76 virtual void PinApp(const std::string& extension_id) OVERRIDE {}
77 virtual void UnpinApp(const std::string& extension_id) OVERRIDE {}
78 virtual Pinnable GetPinnable() OVERRIDE { return NO_PIN; }
79 virtual bool CanDoCreateShortcutsFlow() OVERRIDE { return false; }
80 virtual void DoCreateShortcutsFlow(Profile* profile,
81 const std::string& extension_id) OVERRIDE {
83 virtual bool CanDoShowAppInfoFlow() OVERRIDE { return false; }
84 virtual void DoShowAppInfoFlow(Profile* profile,
85 const std::string& extension_id) OVERRIDE {
87 virtual void CreateNewWindow(Profile* profile, bool incognito) OVERRIDE {}
88 virtual void ActivateApp(Profile* profile,
89 const extensions::Extension* extension,
91 int event_flags) OVERRIDE {}
92 virtual void LaunchApp(Profile* profile,
93 const extensions::Extension* extension,
95 int event_flags) OVERRIDE {}
96 virtual void ShowForProfileByPath(
97 const base::FilePath& profile_path) OVERRIDE {}
98 virtual bool ShouldShowUserIcon() OVERRIDE { return false; }
101 const char kDefaultApps[] = "Packaged App 1,Packaged App 2,Hosted App";
102 const size_t kDefaultAppCount = 3u;
106 class ExtensionAppModelBuilderTest : public ExtensionServiceTestBase {
108 ExtensionAppModelBuilderTest() {}
109 virtual ~ExtensionAppModelBuilderTest() {}
111 virtual void SetUp() OVERRIDE {
112 ExtensionServiceTestBase::SetUp();
114 // Load "app_list" extensions test profile.
115 // The test profile has 4 extensions:
116 // 1 dummy extension, 2 packaged extension apps and 1 hosted extension app.
117 base::FilePath source_install_dir = data_dir_
118 .AppendASCII("app_list")
119 .AppendASCII("Extensions");
120 base::FilePath pref_path = source_install_dir
122 .Append(chrome::kPreferencesFilename);
123 InitializeInstalledExtensionService(pref_path, source_install_dir);
126 // There should be 4 extensions in the test profile.
127 const extensions::ExtensionSet* extensions = service_->extensions();
128 ASSERT_EQ(static_cast<size_t>(4), extensions->size());
133 virtual void TearDown() OVERRIDE {
138 // Creates a new builder, destroying any existing one.
139 void CreateBuilder() {
140 ResetBuilder(); // Destroy any existing builder in the correct order.
142 model_.reset(new app_list::AppListModel);
143 controller_.reset(new TestAppListControllerDelegate);
144 builder_.reset(new ExtensionAppModelBuilder(controller_.get()));
145 builder_->InitializeWithProfile(profile_.get(), model_.get());
148 void ResetBuilder() {
154 scoped_ptr<app_list::AppListModel> model_;
155 scoped_ptr<TestAppListControllerDelegate> controller_;
156 scoped_ptr<ExtensionAppModelBuilder> builder_;
158 base::ScopedTempDir second_profile_temp_dir_;
161 DISALLOW_COPY_AND_ASSIGN(ExtensionAppModelBuilderTest);
164 TEST_F(ExtensionAppModelBuilderTest, Build) {
165 // The apps list would have 3 extension apps in the profile.
166 EXPECT_EQ(kDefaultAppCount, model_->item_list()->item_count());
167 EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
170 TEST_F(ExtensionAppModelBuilderTest, HideWebStore) {
171 // Install a "web store" app.
172 scoped_refptr<extensions::Extension> store =
176 std::string(extension_misc::kWebStoreAppId));
177 service_->AddExtension(store.get());
179 // Install an "enterprise web store" app.
180 scoped_refptr<extensions::Extension> enterprise_store =
181 MakeApp("enterprise_webstore",
184 std::string(extension_misc::kEnterpriseWebStoreAppId));
185 service_->AddExtension(enterprise_store.get());
187 // Web stores should be present in the AppListModel.
188 app_list::AppListModel model1;
189 ExtensionAppModelBuilder builder1(controller_.get());
190 builder1.InitializeWithProfile(profile_.get(), &model1);
191 std::string content = GetModelContent(&model1);
192 EXPECT_NE(std::string::npos, content.find("webstore"));
193 EXPECT_NE(std::string::npos, content.find("enterprise_webstore"));
195 // Activate the HideWebStoreIcon policy.
196 profile_->GetPrefs()->SetBoolean(prefs::kHideWebStoreIcon, true);
198 // Web stores should NOT be in the AppListModel.
199 app_list::AppListModel model2;
200 ExtensionAppModelBuilder builder2(controller_.get());
201 builder2.InitializeWithProfile(profile_.get(), &model2);
202 content = GetModelContent(&model2);
203 EXPECT_EQ(std::string::npos, content.find("webstore"));
204 EXPECT_EQ(std::string::npos, content.find("enterprise_webstore"));
207 TEST_F(ExtensionAppModelBuilderTest, DisableAndEnable) {
208 service_->DisableExtension(kHostedAppId,
209 extensions::Extension::DISABLE_NONE);
210 EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
212 service_->EnableExtension(kHostedAppId);
213 EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
216 TEST_F(ExtensionAppModelBuilderTest, Uninstall) {
217 service_->UninstallExtension(kPackagedApp2Id, false, NULL);
218 EXPECT_EQ(std::string("Packaged App 1,Hosted App"),
219 GetModelContent(model_.get()));
221 base::RunLoop().RunUntilIdle();
224 TEST_F(ExtensionAppModelBuilderTest, UninstallTerminatedApp) {
225 const extensions::Extension* app =
226 service_->GetInstalledExtension(kPackagedApp2Id);
227 ASSERT_TRUE(app != NULL);
229 // Simulate an app termination.
230 service_->TrackTerminatedExtensionForTest(app);
232 service_->UninstallExtension(kPackagedApp2Id, false, NULL);
233 EXPECT_EQ(std::string("Packaged App 1,Hosted App"),
234 GetModelContent(model_.get()));
236 base::RunLoop().RunUntilIdle();
239 TEST_F(ExtensionAppModelBuilderTest, Reinstall) {
240 EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
242 // Install kPackagedApp1Id again should not create a new entry.
243 extensions::InstallTracker* tracker =
244 extensions::InstallTrackerFactory::GetForProfile(profile_.get());
245 extensions::InstallObserver::ExtensionInstallParams params(
246 kPackagedApp1Id, "", gfx::ImageSkia(), true, true);
247 tracker->OnBeginExtensionInstall(params);
249 EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
252 TEST_F(ExtensionAppModelBuilderTest, OrdinalPrefsChange) {
253 extensions::AppSorting* sorting = service_->extension_prefs()->app_sorting();
255 syncer::StringOrdinal package_app_page =
256 sorting->GetPageOrdinal(kPackagedApp1Id);
257 sorting->SetPageOrdinal(kHostedAppId, package_app_page.CreateBefore());
258 // Old behavior: This would be "Hosted App,Packaged App 1,Packaged App 2"
259 // New behavior: Sorting order doesn't change.
260 EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
262 syncer::StringOrdinal app1_ordinal =
263 sorting->GetAppLaunchOrdinal(kPackagedApp1Id);
264 syncer::StringOrdinal app2_ordinal =
265 sorting->GetAppLaunchOrdinal(kPackagedApp2Id);
266 sorting->SetPageOrdinal(kHostedAppId, package_app_page);
267 sorting->SetAppLaunchOrdinal(kHostedAppId,
268 app1_ordinal.CreateBetween(app2_ordinal));
269 // Old behavior: This would be "Packaged App 1,Hosted App,Packaged App 2"
270 // New behavior: Sorting order doesn't change.
271 EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
274 TEST_F(ExtensionAppModelBuilderTest, OnExtensionMoved) {
275 extensions::AppSorting* sorting = service_->extension_prefs()->app_sorting();
276 sorting->SetPageOrdinal(kHostedAppId,
277 sorting->GetPageOrdinal(kPackagedApp1Id));
279 service_->OnExtensionMoved(kHostedAppId, kPackagedApp1Id, kPackagedApp2Id);
280 // Old behavior: This would be "Packaged App 1,Hosted App,Packaged App 2"
281 // New behavior: Sorting order doesn't change.
282 EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
284 service_->OnExtensionMoved(kHostedAppId, kPackagedApp2Id, std::string());
285 // Old behavior: This would be restored to the default order.
286 // New behavior: Sorting order still doesn't change.
287 EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
289 service_->OnExtensionMoved(kHostedAppId, std::string(), kPackagedApp1Id);
290 // Old behavior: This would be "Hosted App,Packaged App 1,Packaged App 2"
291 // New behavior: Sorting order doesn't change.
292 EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
295 TEST_F(ExtensionAppModelBuilderTest, InvalidOrdinal) {
296 // Creates a no-ordinal case.
297 extensions::AppSorting* sorting = service_->extension_prefs()->app_sorting();
298 sorting->ClearOrdinals(kPackagedApp1Id);
300 // Creates a corrupted ordinal case.
301 extensions::ExtensionScopedPrefs* scoped_prefs = service_->extension_prefs();
302 scoped_prefs->UpdateExtensionPref(
305 base::Value::CreateStringValue("a corrupted ordinal"));
307 // This should not assert or crash.
311 TEST_F(ExtensionAppModelBuilderTest, OrdinalConfilicts) {
312 // Creates conflict ordinals for app1 and app2.
313 syncer::StringOrdinal conflict_ordinal =
314 syncer::StringOrdinal::CreateInitialOrdinal();
316 extensions::AppSorting* sorting = service_->extension_prefs()->app_sorting();
317 sorting->SetPageOrdinal(kHostedAppId, conflict_ordinal);
318 sorting->SetAppLaunchOrdinal(kHostedAppId, conflict_ordinal);
320 sorting->SetPageOrdinal(kPackagedApp1Id, conflict_ordinal);
321 sorting->SetAppLaunchOrdinal(kPackagedApp1Id, conflict_ordinal);
323 sorting->SetPageOrdinal(kPackagedApp2Id, conflict_ordinal);
324 sorting->SetAppLaunchOrdinal(kPackagedApp2Id, conflict_ordinal);
326 // This should not assert or crash.
329 // By default, conflicted items are sorted by their app ids (= order added).
330 EXPECT_EQ(std::string("Hosted App,Packaged App 1,Packaged App 2"),
331 GetModelContent(model_.get()));
334 // This test adds a bookmark app to the app list.
335 TEST_F(ExtensionAppModelBuilderTest, BookmarkApp) {
336 const std::string kAppName = "Bookmark App";
337 const std::string kAppVersion = "2014.1.24.19748";
338 const std::string kAppUrl = "http://google.com";
339 const std::string kAppId = "podhdnefolignjhecmjkbimfgioanahm";
341 base::DictionaryValue value;
342 value.SetString("name", kAppName);
343 value.SetString("version", kAppVersion);
344 value.SetString("app.launch.web_url", kAppUrl);
345 scoped_refptr<extensions::Extension> bookmark_app =
346 extensions::Extension::Create(
348 extensions::Manifest::INTERNAL,
350 extensions::Extension::WAS_INSTALLED_BY_DEFAULT |
351 extensions::Extension::FROM_BOOKMARK,
354 EXPECT_TRUE(err.empty());
356 service_->AddExtension(bookmark_app.get());
357 EXPECT_EQ(kDefaultAppCount + 1, model_->item_list()->item_count());
358 EXPECT_NE(std::string::npos, GetModelContent(model_.get()).find(kAppName));