extension_app_model_builder_unittest.cc revision 5f1c94371a64b3196d4be9466099bb892df9b88e
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.
4
5#include "chrome/browser/ui/app_list/extension_app_model_builder.h"
6
7#include <string>
8
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.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/browser/ui/app_list/app_list_test_util.h"
20#include "chrome/common/chrome_constants.h"
21#include "chrome/common/extensions/extension_constants.h"
22#include "chrome/common/pref_names.h"
23#include "chrome/test/base/testing_profile.h"
24#include "extensions/browser/app_sorting.h"
25#include "extensions/browser/extension_prefs.h"
26#include "extensions/browser/uninstall_reason.h"
27#include "extensions/common/extension_set.h"
28#include "extensions/common/manifest.h"
29#include "testing/gtest/include/gtest/gtest.h"
30#include "ui/app_list/app_list_item.h"
31
32namespace {
33
34// Get a string of all apps in |model| joined with ','.
35std::string GetModelContent(app_list::AppListModel* model) {
36  std::string content;
37  for (size_t i = 0; i < model->top_level_item_list()->item_count(); ++i) {
38    if (i > 0)
39      content += ',';
40    content += model->top_level_item_list()->item_at(i)->name();
41  }
42  return content;
43}
44
45scoped_refptr<extensions::Extension> MakeApp(const std::string& name,
46                                             const std::string& version,
47                                             const std::string& url,
48                                             const std::string& id) {
49  std::string err;
50  base::DictionaryValue value;
51  value.SetString("name", name);
52  value.SetString("version", version);
53  value.SetString("app.launch.web_url", url);
54  scoped_refptr<extensions::Extension> app =
55      extensions::Extension::Create(
56          base::FilePath(),
57          extensions::Manifest::INTERNAL,
58          value,
59          extensions::Extension::WAS_INSTALLED_BY_DEFAULT,
60          id,
61          &err);
62  EXPECT_EQ(err, "");
63  return app;
64}
65
66class TestAppListControllerDelegate : public AppListControllerDelegate {
67 public:
68  virtual ~TestAppListControllerDelegate() {}
69  virtual void DismissView() OVERRIDE {}
70  virtual gfx::NativeWindow GetAppListWindow() OVERRIDE { return NULL; }
71  virtual gfx::ImageSkia GetWindowIcon() OVERRIDE { return gfx::ImageSkia(); }
72  virtual bool IsAppPinned(const std::string& extension_id) OVERRIDE {
73    return false;
74  }
75  virtual void PinApp(const std::string& extension_id) OVERRIDE {}
76  virtual void UnpinApp(const std::string& extension_id) OVERRIDE {}
77  virtual Pinnable GetPinnable() OVERRIDE { return NO_PIN; }
78  virtual bool CanDoCreateShortcutsFlow() OVERRIDE { return false; }
79  virtual void DoCreateShortcutsFlow(Profile* profile,
80                                     const std::string& extension_id) OVERRIDE {
81  }
82  virtual bool CanDoShowAppInfoFlow() OVERRIDE { return false; }
83  virtual void DoShowAppInfoFlow(Profile* profile,
84                                 const std::string& extension_id) OVERRIDE {
85  };
86  virtual void CreateNewWindow(Profile* profile, bool incognito) OVERRIDE {}
87  virtual void ActivateApp(Profile* profile,
88                           const extensions::Extension* extension,
89                           AppListSource source,
90                           int event_flags) OVERRIDE {}
91  virtual void LaunchApp(Profile* profile,
92                         const extensions::Extension* extension,
93                         AppListSource source,
94                         int event_flags) OVERRIDE {}
95  virtual void ShowForProfileByPath(
96      const base::FilePath& profile_path) OVERRIDE {}
97  virtual bool ShouldShowUserIcon() OVERRIDE { return false; }
98};
99
100const char kDefaultApps[] = "Packaged App 1,Packaged App 2,Hosted App";
101const size_t kDefaultAppCount = 3u;
102
103}  // namespace
104
105class ExtensionAppModelBuilderTest : public AppListTestBase {
106 public:
107  ExtensionAppModelBuilderTest() {}
108  virtual ~ExtensionAppModelBuilderTest() {}
109
110  virtual void SetUp() OVERRIDE {
111    AppListTestBase::SetUp();
112
113    CreateBuilder();
114  }
115
116  virtual void TearDown() OVERRIDE {
117    ResetBuilder();
118  }
119
120 protected:
121  // Creates a new builder, destroying any existing one.
122  void CreateBuilder() {
123    ResetBuilder();  // Destroy any existing builder in the correct order.
124
125    model_.reset(new app_list::AppListModel);
126    controller_.reset(new TestAppListControllerDelegate);
127    builder_.reset(new ExtensionAppModelBuilder(controller_.get()));
128    builder_->InitializeWithProfile(profile_.get(), model_.get());
129  }
130
131  void ResetBuilder() {
132    builder_.reset();
133    controller_.reset();
134    model_.reset();
135  }
136
137  scoped_ptr<app_list::AppListModel> model_;
138  scoped_ptr<TestAppListControllerDelegate> controller_;
139  scoped_ptr<ExtensionAppModelBuilder> builder_;
140
141  base::ScopedTempDir second_profile_temp_dir_;
142
143 private:
144  DISALLOW_COPY_AND_ASSIGN(ExtensionAppModelBuilderTest);
145};
146
147TEST_F(ExtensionAppModelBuilderTest, Build) {
148  // The apps list would have 3 extension apps in the profile.
149  EXPECT_EQ(kDefaultAppCount, model_->top_level_item_list()->item_count());
150  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
151}
152
153TEST_F(ExtensionAppModelBuilderTest, HideWebStore) {
154  // Install a "web store" app.
155  scoped_refptr<extensions::Extension> store =
156      MakeApp("webstore",
157              "0.0",
158              "http://google.com",
159              std::string(extension_misc::kWebStoreAppId));
160  service_->AddExtension(store.get());
161
162  // Install an "enterprise web store" app.
163  scoped_refptr<extensions::Extension> enterprise_store =
164      MakeApp("enterprise_webstore",
165              "0.0",
166              "http://google.com",
167              std::string(extension_misc::kEnterpriseWebStoreAppId));
168  service_->AddExtension(enterprise_store.get());
169
170  // Web stores should be present in the AppListModel.
171  app_list::AppListModel model1;
172  ExtensionAppModelBuilder builder1(controller_.get());
173  builder1.InitializeWithProfile(profile_.get(), &model1);
174  EXPECT_TRUE(model1.FindItem(store->id()));
175  EXPECT_TRUE(model1.FindItem(enterprise_store->id()));
176
177  // Activate the HideWebStoreIcon policy.
178  profile_->GetPrefs()->SetBoolean(prefs::kHideWebStoreIcon, true);
179
180  // Now the web stores should not be present anymore.
181  EXPECT_FALSE(model1.FindItem(store->id()));
182  EXPECT_FALSE(model1.FindItem(enterprise_store->id()));
183
184  // Build a new AppListModel; web stores should NOT be present.
185  app_list::AppListModel model2;
186  ExtensionAppModelBuilder builder2(controller_.get());
187  builder2.InitializeWithProfile(profile_.get(), &model2);
188  EXPECT_FALSE(model2.FindItem(store->id()));
189  EXPECT_FALSE(model2.FindItem(enterprise_store->id()));
190
191  // Deactivate the HideWebStoreIcon policy again.
192  profile_->GetPrefs()->SetBoolean(prefs::kHideWebStoreIcon, false);
193
194  // Now the web stores should have appeared.
195  EXPECT_TRUE(model2.FindItem(store->id()));
196  EXPECT_TRUE(model2.FindItem(enterprise_store->id()));
197}
198
199TEST_F(ExtensionAppModelBuilderTest, DisableAndEnable) {
200  service_->DisableExtension(kHostedAppId,
201                             extensions::Extension::DISABLE_NONE);
202  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
203
204  service_->EnableExtension(kHostedAppId);
205  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
206}
207
208TEST_F(ExtensionAppModelBuilderTest, Uninstall) {
209  service_->UninstallExtension(kPackagedApp2Id,
210                               extensions::UNINSTALL_REASON_FOR_TESTING,
211                               base::Bind(&base::DoNothing),
212                               NULL);
213  EXPECT_EQ(std::string("Packaged App 1,Hosted App"),
214            GetModelContent(model_.get()));
215
216  base::RunLoop().RunUntilIdle();
217}
218
219TEST_F(ExtensionAppModelBuilderTest, UninstallTerminatedApp) {
220  const extensions::Extension* app =
221      service_->GetInstalledExtension(kPackagedApp2Id);
222  ASSERT_TRUE(app != NULL);
223
224  // Simulate an app termination.
225  service_->TrackTerminatedExtensionForTest(app);
226
227  service_->UninstallExtension(kPackagedApp2Id,
228                               extensions::UNINSTALL_REASON_FOR_TESTING,
229                               base::Bind(&base::DoNothing),
230                               NULL);
231  EXPECT_EQ(std::string("Packaged App 1,Hosted App"),
232            GetModelContent(model_.get()));
233
234  base::RunLoop().RunUntilIdle();
235}
236
237TEST_F(ExtensionAppModelBuilderTest, Reinstall) {
238  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
239
240  // Install kPackagedApp1Id again should not create a new entry.
241  extensions::InstallTracker* tracker =
242      extensions::InstallTrackerFactory::GetForBrowserContext(profile_.get());
243  extensions::InstallObserver::ExtensionInstallParams params(
244      kPackagedApp1Id, "", gfx::ImageSkia(), true, true);
245  tracker->OnBeginExtensionInstall(params);
246
247  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
248}
249
250TEST_F(ExtensionAppModelBuilderTest, OrdinalPrefsChange) {
251  extensions::AppSorting* sorting =
252      extensions::ExtensionPrefs::Get(profile_.get())->app_sorting();
253
254  syncer::StringOrdinal package_app_page =
255      sorting->GetPageOrdinal(kPackagedApp1Id);
256  sorting->SetPageOrdinal(kHostedAppId, package_app_page.CreateBefore());
257  // Old behavior: This would be "Hosted App,Packaged App 1,Packaged App 2"
258  // New behavior: Sorting order doesn't change.
259  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
260
261  syncer::StringOrdinal app1_ordinal =
262      sorting->GetAppLaunchOrdinal(kPackagedApp1Id);
263  syncer::StringOrdinal app2_ordinal =
264      sorting->GetAppLaunchOrdinal(kPackagedApp2Id);
265  sorting->SetPageOrdinal(kHostedAppId, package_app_page);
266  sorting->SetAppLaunchOrdinal(kHostedAppId,
267                               app1_ordinal.CreateBetween(app2_ordinal));
268  // Old behavior: This would be "Packaged App 1,Hosted App,Packaged App 2"
269  // New behavior: Sorting order doesn't change.
270  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
271}
272
273TEST_F(ExtensionAppModelBuilderTest, OnExtensionMoved) {
274  extensions::AppSorting* sorting =
275      extensions::ExtensionPrefs::Get(profile_.get())->app_sorting();
276  sorting->SetPageOrdinal(kHostedAppId,
277                          sorting->GetPageOrdinal(kPackagedApp1Id));
278
279  sorting->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()));
283
284  sorting->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()));
288
289  sorting->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()));
293}
294
295TEST_F(ExtensionAppModelBuilderTest, InvalidOrdinal) {
296  // Creates a no-ordinal case.
297  extensions::AppSorting* sorting =
298      extensions::ExtensionPrefs::Get(profile_.get())->app_sorting();
299  sorting->ClearOrdinals(kPackagedApp1Id);
300
301  // Creates a corrupted ordinal case.
302  extensions::ExtensionScopedPrefs* scoped_prefs =
303      extensions::ExtensionPrefs::Get(profile_.get());
304  scoped_prefs->UpdateExtensionPref(
305      kHostedAppId,
306      "page_ordinal",
307      new base::StringValue("a corrupted ordinal"));
308
309  // This should not assert or crash.
310  CreateBuilder();
311}
312
313TEST_F(ExtensionAppModelBuilderTest, OrdinalConfilicts) {
314  // Creates conflict ordinals for app1 and app2.
315  syncer::StringOrdinal conflict_ordinal =
316      syncer::StringOrdinal::CreateInitialOrdinal();
317
318  extensions::AppSorting* sorting =
319      extensions::ExtensionPrefs::Get(profile_.get())->app_sorting();
320  sorting->SetPageOrdinal(kHostedAppId, conflict_ordinal);
321  sorting->SetAppLaunchOrdinal(kHostedAppId, conflict_ordinal);
322
323  sorting->SetPageOrdinal(kPackagedApp1Id, conflict_ordinal);
324  sorting->SetAppLaunchOrdinal(kPackagedApp1Id, conflict_ordinal);
325
326  sorting->SetPageOrdinal(kPackagedApp2Id, conflict_ordinal);
327  sorting->SetAppLaunchOrdinal(kPackagedApp2Id, conflict_ordinal);
328
329  // This should not assert or crash.
330  CreateBuilder();
331
332  // By default, conflicted items are sorted by their app ids (= order added).
333  EXPECT_EQ(std::string("Hosted App,Packaged App 1,Packaged App 2"),
334            GetModelContent(model_.get()));
335}
336
337// This test adds a bookmark app to the app list.
338TEST_F(ExtensionAppModelBuilderTest, BookmarkApp) {
339  const std::string kAppName = "Bookmark App";
340  const std::string kAppVersion = "2014.1.24.19748";
341  const std::string kAppUrl = "http://google.com";
342  const std::string kAppId = "podhdnefolignjhecmjkbimfgioanahm";
343  std::string err;
344  base::DictionaryValue value;
345  value.SetString("name", kAppName);
346  value.SetString("version", kAppVersion);
347  value.SetString("app.launch.web_url", kAppUrl);
348  scoped_refptr<extensions::Extension> bookmark_app =
349      extensions::Extension::Create(
350          base::FilePath(),
351          extensions::Manifest::INTERNAL,
352          value,
353          extensions::Extension::WAS_INSTALLED_BY_DEFAULT |
354              extensions::Extension::FROM_BOOKMARK,
355          kAppId,
356          &err);
357  EXPECT_TRUE(err.empty());
358
359  service_->AddExtension(bookmark_app.get());
360  EXPECT_EQ(kDefaultAppCount + 1, model_->top_level_item_list()->item_count());
361  EXPECT_NE(std::string::npos, GetModelContent(model_.get()).find(kAppName));
362}
363