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