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/apps_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_unittest.h" 16 #include "chrome/browser/extensions/extension_sorting.h" 17 #include "chrome/common/extensions/extension_constants.h" 18 #include "chrome/common/extensions/manifest.h" 19 #include "chrome/common/pref_names.h" 20 #include "chrome/test/base/testing_profile.h" 21 #include "testing/gtest/include/gtest/gtest.h" 22 #include "ui/app_list/app_list_item_model.h" 23 24 namespace { 25 26 const char kHostedAppId[] = "dceacbkfkmllgmjmbhgkpjegnodmildf"; 27 const char kPackagedApp1Id[] = "emfkafnhnpcmabnnkckkchdilgeoekbo"; 28 const char kPackagedApp2Id[] = "jlklkagmeajbjiobondfhiekepofmljl"; 29 30 // Get a string of all apps in |model| joined with ','. 31 std::string GetModelContent(app_list::AppListModel::Apps* model) { 32 std::string content; 33 for (size_t i = 0; i < model->item_count(); ++i) { 34 if (i > 0) 35 content += ','; 36 content += model->GetItemAt(i)->title(); 37 } 38 return content; 39 } 40 41 scoped_refptr<extensions::Extension> MakeApp(const std::string& name, 42 const std::string& version, 43 const std::string& url, 44 const std::string& id) { 45 std::string err; 46 DictionaryValue value; 47 value.SetString("name", name); 48 value.SetString("version", version); 49 value.SetString("app.launch.web_url", url); 50 scoped_refptr<extensions::Extension> app = 51 extensions::Extension::Create( 52 base::FilePath(), 53 extensions::Manifest::INTERNAL, 54 value, 55 extensions::Extension::WAS_INSTALLED_BY_DEFAULT, 56 id, 57 &err); 58 EXPECT_EQ(err, ""); 59 return app; 60 } 61 62 } // namespace 63 64 class AppsModelBuilderTest : public ExtensionServiceTestBase { 65 public: 66 AppsModelBuilderTest() {} 67 virtual ~AppsModelBuilderTest() {} 68 69 virtual void SetUp() OVERRIDE { 70 ExtensionServiceTestBase::SetUp(); 71 72 // Load "app_list" extensions test profile. 73 // The test profile has 4 extensions: 74 // 1 dummy extension, 2 packaged extension apps and 1 hosted extension app. 75 base::FilePath source_install_dir = data_dir_ 76 .AppendASCII("app_list") 77 .AppendASCII("Extensions"); 78 base::FilePath pref_path = source_install_dir 79 .DirName() 80 .AppendASCII("Preferences"); 81 InitializeInstalledExtensionService(pref_path, source_install_dir); 82 service_->Init(); 83 84 // There should be 4 extensions in the test profile. 85 const ExtensionSet* extensions = service_->extensions(); 86 ASSERT_EQ(static_cast<size_t>(4), extensions->size()); 87 } 88 }; 89 90 TEST_F(AppsModelBuilderTest, Build) { 91 scoped_ptr<app_list::AppListModel::Apps> model( 92 new app_list::AppListModel::Apps); 93 AppsModelBuilder builder(profile_.get(), model.get(), NULL); 94 builder.Build(); 95 96 // The apps list would have 3 extension apps in the profile. 97 EXPECT_EQ(std::string("Packaged App 1,Packaged App 2,Hosted App"), 98 GetModelContent(model.get())); 99 } 100 101 TEST_F(AppsModelBuilderTest, HideWebStore) { 102 // Install a "web store" app. 103 scoped_refptr<extensions::Extension> store = 104 MakeApp("webstore", 105 "0.0", 106 "http://google.com", 107 std::string(extension_misc::kWebStoreAppId)); 108 service_->AddExtension(store.get()); 109 110 // Install an "enterprise web store" app. 111 scoped_refptr<extensions::Extension> enterprise_store = 112 MakeApp("enterprise_webstore", 113 "0.0", 114 "http://google.com", 115 std::string(extension_misc::kEnterpriseWebStoreAppId)); 116 service_->AddExtension(enterprise_store.get()); 117 118 // Web stores should be present in the AppListModel. 119 app_list::AppListModel::Apps model1; 120 AppsModelBuilder builder1(profile_.get(), &model1, NULL); 121 builder1.Build(); 122 std::string content = GetModelContent(&model1); 123 EXPECT_NE(std::string::npos, content.find("webstore")); 124 EXPECT_NE(std::string::npos, content.find("enterprise_webstore")); 125 126 // Activate the HideWebStoreIcon policy. 127 profile_->GetPrefs()->SetBoolean(prefs::kHideWebStoreIcon, true); 128 129 // Web stores should NOT be in the AppListModel. 130 app_list::AppListModel::Apps model2; 131 AppsModelBuilder builder2(profile_.get(), &model2, NULL); 132 builder2.Build(); 133 content = GetModelContent(&model2); 134 EXPECT_EQ(std::string::npos, content.find("webstore")); 135 EXPECT_EQ(std::string::npos, content.find("enterprise_webstore")); 136 } 137 138 TEST_F(AppsModelBuilderTest, DisableAndEnable) { 139 scoped_ptr<app_list::AppListModel::Apps> model( 140 new app_list::AppListModel::Apps); 141 AppsModelBuilder builder(profile_.get(), model.get(), NULL); 142 builder.Build(); 143 144 service_->DisableExtension(kHostedAppId, 145 extensions::Extension::DISABLE_NONE); 146 EXPECT_EQ(std::string("Packaged App 1,Packaged App 2,Hosted App"), 147 GetModelContent(model.get())); 148 149 service_->EnableExtension(kHostedAppId); 150 EXPECT_EQ(std::string("Packaged App 1,Packaged App 2,Hosted App"), 151 GetModelContent(model.get())); 152 } 153 154 TEST_F(AppsModelBuilderTest, Uninstall) { 155 scoped_ptr<app_list::AppListModel::Apps> model( 156 new app_list::AppListModel::Apps); 157 AppsModelBuilder builder(profile_.get(), model.get(), NULL); 158 builder.Build(); 159 160 service_->UninstallExtension(kPackagedApp2Id, false, NULL); 161 EXPECT_EQ(std::string("Packaged App 1,Hosted App"), 162 GetModelContent(model.get())); 163 164 base::RunLoop().RunUntilIdle(); 165 } 166 167 TEST_F(AppsModelBuilderTest, UninstallTerminatedApp) { 168 scoped_ptr<app_list::AppListModel::Apps> model( 169 new app_list::AppListModel::Apps); 170 AppsModelBuilder builder(profile_.get(), model.get(), NULL); 171 builder.Build(); 172 173 const extensions::Extension* app = 174 service_->GetInstalledExtension(kPackagedApp2Id); 175 ASSERT_TRUE(app != NULL); 176 177 // Simulate an app termination. 178 service_->TrackTerminatedExtensionForTest(app); 179 180 service_->UninstallExtension(kPackagedApp2Id, false, NULL); 181 EXPECT_EQ(std::string("Packaged App 1,Hosted App"), 182 GetModelContent(model.get())); 183 184 base::RunLoop().RunUntilIdle(); 185 } 186 187 TEST_F(AppsModelBuilderTest, OrdinalPrefsChange) { 188 scoped_ptr<app_list::AppListModel::Apps> model( 189 new app_list::AppListModel::Apps); 190 AppsModelBuilder builder(profile_.get(), model.get(), NULL); 191 builder.Build(); 192 193 ExtensionSorting* sorting = service_->extension_prefs()->extension_sorting(); 194 195 syncer::StringOrdinal package_app_page = 196 sorting->GetPageOrdinal(kPackagedApp1Id); 197 sorting->SetPageOrdinal(kHostedAppId, package_app_page.CreateBefore()); 198 EXPECT_EQ(std::string("Hosted App,Packaged App 1,Packaged App 2"), 199 GetModelContent(model.get())); 200 201 syncer::StringOrdinal app1_ordinal = 202 sorting->GetAppLaunchOrdinal(kPackagedApp1Id); 203 syncer::StringOrdinal app2_ordinal = 204 sorting->GetAppLaunchOrdinal(kPackagedApp2Id); 205 sorting->SetPageOrdinal(kHostedAppId, package_app_page); 206 sorting->SetAppLaunchOrdinal(kHostedAppId, 207 app1_ordinal.CreateBetween(app2_ordinal)); 208 EXPECT_EQ(std::string("Packaged App 1,Hosted App,Packaged App 2"), 209 GetModelContent(model.get())); 210 } 211 212 TEST_F(AppsModelBuilderTest, OnExtensionMoved) { 213 scoped_ptr<app_list::AppListModel::Apps> model( 214 new app_list::AppListModel::Apps); 215 AppsModelBuilder builder(profile_.get(), model.get(), NULL); 216 builder.Build(); 217 218 ExtensionSorting* sorting = service_->extension_prefs()->extension_sorting(); 219 sorting->SetPageOrdinal(kHostedAppId, 220 sorting->GetPageOrdinal(kPackagedApp1Id)); 221 222 service_->OnExtensionMoved(kHostedAppId, kPackagedApp1Id, kPackagedApp2Id); 223 EXPECT_EQ(std::string("Packaged App 1,Hosted App,Packaged App 2"), 224 GetModelContent(model.get())); 225 226 service_->OnExtensionMoved(kHostedAppId, kPackagedApp2Id, std::string()); 227 EXPECT_EQ(std::string("Packaged App 1,Packaged App 2,Hosted App"), 228 GetModelContent(model.get())); 229 230 service_->OnExtensionMoved(kHostedAppId, std::string(), kPackagedApp1Id); 231 EXPECT_EQ(std::string("Hosted App,Packaged App 1,Packaged App 2"), 232 GetModelContent(model.get())); 233 } 234 235 TEST_F(AppsModelBuilderTest, InvalidOrdinal) { 236 // Creates a no-ordinal case. 237 ExtensionSorting* sorting = service_->extension_prefs()->extension_sorting(); 238 sorting->ClearOrdinals(kPackagedApp1Id); 239 240 // Creates an corrupted ordinal case. 241 ExtensionScopedPrefs* scoped_prefs = service_->extension_prefs(); 242 scoped_prefs->UpdateExtensionPref( 243 kHostedAppId, 244 "page_ordinal", 245 base::Value::CreateStringValue("a corrupted ordinal")); 246 247 scoped_ptr<app_list::AppListModel::Apps> model( 248 new app_list::AppListModel::Apps); 249 AppsModelBuilder builder(profile_.get(), model.get(), NULL); 250 251 // This should not assert or crash. 252 builder.Build(); 253 } 254 255 TEST_F(AppsModelBuilderTest, OrdinalConfilicts) { 256 // Creates conflict ordinals for app1 and app2. 257 syncer::StringOrdinal conflict_ordinal = 258 syncer::StringOrdinal::CreateInitialOrdinal(); 259 260 ExtensionSorting* sorting = service_->extension_prefs()->extension_sorting(); 261 sorting->SetPageOrdinal(kHostedAppId, conflict_ordinal); 262 sorting->SetAppLaunchOrdinal(kHostedAppId, conflict_ordinal); 263 264 sorting->SetPageOrdinal(kPackagedApp1Id, conflict_ordinal); 265 sorting->SetAppLaunchOrdinal(kPackagedApp1Id, conflict_ordinal); 266 267 sorting->SetPageOrdinal(kPackagedApp2Id, conflict_ordinal); 268 sorting->SetAppLaunchOrdinal(kPackagedApp2Id, conflict_ordinal); 269 270 scoped_ptr<app_list::AppListModel::Apps> model( 271 new app_list::AppListModel::Apps); 272 AppsModelBuilder builder(profile_.get(), model.get(), NULL); 273 builder.Build(); 274 275 // By default, conflicted items are sorted by their app ids. 276 EXPECT_EQ(std::string("Hosted App,Packaged App 1,Packaged App 2"), 277 GetModelContent(model.get())); 278 279 // Move hosted app between app1 and app2 and it should not crash. 280 service_->OnExtensionMoved(kHostedAppId, kPackagedApp1Id, kPackagedApp2Id); 281 EXPECT_EQ(std::string("Packaged App 1,Hosted App,Packaged App 2"), 282 GetModelContent(model.get())); 283 } 284