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