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/common/extensions/message_bundle.h" 6 7 #include <string> 8 #include <vector> 9 10 #include "base/i18n/rtl.h" 11 #include "base/memory/linked_ptr.h" 12 #include "base/memory/scoped_ptr.h" 13 #include "base/strings/string_util.h" 14 #include "base/strings/utf_string_conversions.h" 15 #include "base/values.h" 16 #include "chrome/common/extensions/extension_l10n_util.h" 17 #include "extensions/common/error_utils.h" 18 #include "extensions/common/manifest_constants.h" 19 #include "testing/gtest/include/gtest/gtest.h" 20 21 namespace extensions { 22 23 namespace errors = manifest_errors; 24 25 class MessageBundleTest : public testing::Test { 26 protected: 27 enum BadDictionary { 28 INVALID_NAME, 29 NAME_NOT_A_TREE, 30 EMPTY_NAME_TREE, 31 MISSING_MESSAGE, 32 PLACEHOLDER_NOT_A_TREE, 33 EMPTY_PLACEHOLDER_TREE, 34 CONTENT_MISSING, 35 MESSAGE_PLACEHOLDER_DOESNT_MATCH, 36 }; 37 38 // Helper method for dictionary building. 39 void SetDictionary(const std::string& name, 40 base::DictionaryValue* subtree, 41 base::DictionaryValue* target) { 42 target->Set(name, static_cast<base::Value*>(subtree)); 43 } 44 45 void CreateContentTree(const std::string& name, 46 const std::string& content, 47 base::DictionaryValue* dict) { 48 base::DictionaryValue* content_tree = new base::DictionaryValue; 49 content_tree->SetString(MessageBundle::kContentKey, content); 50 SetDictionary(name, content_tree, dict); 51 } 52 53 void CreatePlaceholdersTree(base::DictionaryValue* dict) { 54 base::DictionaryValue* placeholders_tree = new base::DictionaryValue; 55 CreateContentTree("a", "A", placeholders_tree); 56 CreateContentTree("b", "B", placeholders_tree); 57 CreateContentTree("c", "C", placeholders_tree); 58 SetDictionary(MessageBundle::kPlaceholdersKey, 59 placeholders_tree, 60 dict); 61 } 62 63 void CreateMessageTree(const std::string& name, 64 const std::string& message, 65 bool create_placeholder_subtree, 66 base::DictionaryValue* dict) { 67 base::DictionaryValue* message_tree = new base::DictionaryValue; 68 if (create_placeholder_subtree) 69 CreatePlaceholdersTree(message_tree); 70 message_tree->SetString(MessageBundle::kMessageKey, message); 71 SetDictionary(name, message_tree, dict); 72 } 73 74 // Caller owns the memory. 75 base::DictionaryValue* CreateGoodDictionary() { 76 base::DictionaryValue* dict = new base::DictionaryValue; 77 CreateMessageTree("n1", "message1 $a$ $b$", true, dict); 78 CreateMessageTree("n2", "message2 $c$", true, dict); 79 CreateMessageTree("n3", "message3", false, dict); 80 return dict; 81 } 82 83 // Caller owns the memory. 84 base::DictionaryValue* CreateBadDictionary(enum BadDictionary what_is_bad) { 85 base::DictionaryValue* dict = CreateGoodDictionary(); 86 // Now remove/break things. 87 switch (what_is_bad) { 88 case INVALID_NAME: 89 CreateMessageTree("n 5", "nevermind", false, dict); 90 break; 91 case NAME_NOT_A_TREE: 92 dict->SetString("n4", "whatever"); 93 break; 94 case EMPTY_NAME_TREE: { 95 base::DictionaryValue* empty_tree = new base::DictionaryValue; 96 SetDictionary("n4", empty_tree, dict); 97 } 98 break; 99 case MISSING_MESSAGE: 100 dict->Remove("n1.message", NULL); 101 break; 102 case PLACEHOLDER_NOT_A_TREE: 103 dict->SetString("n1.placeholders", "whatever"); 104 break; 105 case EMPTY_PLACEHOLDER_TREE: { 106 base::DictionaryValue* empty_tree = new base::DictionaryValue; 107 SetDictionary("n1.placeholders", empty_tree, dict); 108 } 109 break; 110 case CONTENT_MISSING: 111 dict->Remove("n1.placeholders.a.content", NULL); 112 break; 113 case MESSAGE_PLACEHOLDER_DOESNT_MATCH: 114 base::DictionaryValue* value; 115 dict->Remove("n1.placeholders.a", NULL); 116 dict->GetDictionary("n1.placeholders", &value); 117 CreateContentTree("x", "X", value); 118 break; 119 } 120 121 return dict; 122 } 123 124 unsigned int ReservedMessagesCount() { 125 // Update when adding new reserved messages. 126 return 5U; 127 } 128 129 void CheckReservedMessages(MessageBundle* handler) { 130 std::string ui_locale = extension_l10n_util::CurrentLocaleOrDefault(); 131 EXPECT_EQ(ui_locale, 132 handler->GetL10nMessage(MessageBundle::kUILocaleKey)); 133 134 std::string text_dir = "ltr"; 135 if (base::i18n::GetTextDirectionForLocale(ui_locale.c_str()) == 136 base::i18n::RIGHT_TO_LEFT) 137 text_dir = "rtl"; 138 139 EXPECT_EQ(text_dir, handler->GetL10nMessage( 140 MessageBundle::kBidiDirectionKey)); 141 } 142 143 bool AppendReservedMessages(const std::string& application_locale) { 144 std::string error; 145 return handler_->AppendReservedMessagesForLocale( 146 application_locale, &error); 147 } 148 149 std::string CreateMessageBundle() { 150 std::string error; 151 handler_.reset(MessageBundle::Create(catalogs_, &error)); 152 153 return error; 154 } 155 156 void ClearDictionary() { 157 handler_->dictionary_.clear(); 158 } 159 160 scoped_ptr<MessageBundle> handler_; 161 std::vector<linked_ptr<base::DictionaryValue> > catalogs_; 162 }; 163 164 TEST_F(MessageBundleTest, ReservedMessagesCount) { 165 ASSERT_EQ(5U, ReservedMessagesCount()); 166 } 167 168 TEST_F(MessageBundleTest, InitEmptyDictionaries) { 169 CreateMessageBundle(); 170 EXPECT_TRUE(handler_.get() != NULL); 171 EXPECT_EQ(0U + ReservedMessagesCount(), handler_->size()); 172 CheckReservedMessages(handler_.get()); 173 } 174 175 TEST_F(MessageBundleTest, InitGoodDefaultDict) { 176 catalogs_.push_back( 177 linked_ptr<base::DictionaryValue>(CreateGoodDictionary())); 178 CreateMessageBundle(); 179 180 EXPECT_TRUE(handler_.get() != NULL); 181 EXPECT_EQ(3U + ReservedMessagesCount(), handler_->size()); 182 183 EXPECT_EQ("message1 A B", handler_->GetL10nMessage("n1")); 184 EXPECT_EQ("message2 C", handler_->GetL10nMessage("n2")); 185 EXPECT_EQ("message3", handler_->GetL10nMessage("n3")); 186 CheckReservedMessages(handler_.get()); 187 } 188 189 TEST_F(MessageBundleTest, InitAppDictConsultedFirst) { 190 catalogs_.push_back( 191 linked_ptr<base::DictionaryValue>(CreateGoodDictionary())); 192 catalogs_.push_back( 193 linked_ptr<base::DictionaryValue>(CreateGoodDictionary())); 194 195 base::DictionaryValue* app_dict = catalogs_[0].get(); 196 // Flip placeholders in message of n1 tree. 197 app_dict->SetString("n1.message", "message1 $b$ $a$"); 198 // Remove one message from app dict. 199 app_dict->Remove("n2", NULL); 200 // Replace n3 with N3. 201 app_dict->Remove("n3", NULL); 202 CreateMessageTree("N3", "message3_app_dict", false, app_dict); 203 204 CreateMessageBundle(); 205 206 EXPECT_TRUE(handler_.get() != NULL); 207 EXPECT_EQ(3U + ReservedMessagesCount(), handler_->size()); 208 209 EXPECT_EQ("message1 B A", handler_->GetL10nMessage("n1")); 210 EXPECT_EQ("message2 C", handler_->GetL10nMessage("n2")); 211 EXPECT_EQ("message3_app_dict", handler_->GetL10nMessage("n3")); 212 CheckReservedMessages(handler_.get()); 213 } 214 215 TEST_F(MessageBundleTest, InitBadAppDict) { 216 catalogs_.push_back( 217 linked_ptr<base::DictionaryValue>(CreateBadDictionary(INVALID_NAME))); 218 catalogs_.push_back( 219 linked_ptr<base::DictionaryValue>(CreateGoodDictionary())); 220 221 std::string error = CreateMessageBundle(); 222 223 EXPECT_TRUE(handler_.get() == NULL); 224 EXPECT_EQ("Name of a key \"n 5\" is invalid. Only ASCII [a-z], " 225 "[A-Z], [0-9] and \"_\" are allowed.", error); 226 227 catalogs_[0].reset(CreateBadDictionary(NAME_NOT_A_TREE)); 228 handler_.reset(MessageBundle::Create(catalogs_, &error)); 229 EXPECT_TRUE(handler_.get() == NULL); 230 EXPECT_EQ("Not a valid tree for key n4.", error); 231 232 catalogs_[0].reset(CreateBadDictionary(EMPTY_NAME_TREE)); 233 handler_.reset(MessageBundle::Create(catalogs_, &error)); 234 EXPECT_TRUE(handler_.get() == NULL); 235 EXPECT_EQ("There is no \"message\" element for key n4.", error); 236 237 catalogs_[0].reset(CreateBadDictionary(MISSING_MESSAGE)); 238 handler_.reset(MessageBundle::Create(catalogs_, &error)); 239 EXPECT_TRUE(handler_.get() == NULL); 240 EXPECT_EQ("There is no \"message\" element for key n1.", error); 241 242 catalogs_[0].reset(CreateBadDictionary(PLACEHOLDER_NOT_A_TREE)); 243 handler_.reset(MessageBundle::Create(catalogs_, &error)); 244 EXPECT_TRUE(handler_.get() == NULL); 245 EXPECT_EQ("Not a valid \"placeholders\" element for key n1.", error); 246 247 catalogs_[0].reset(CreateBadDictionary(EMPTY_PLACEHOLDER_TREE)); 248 handler_.reset(MessageBundle::Create(catalogs_, &error)); 249 EXPECT_TRUE(handler_.get() == NULL); 250 EXPECT_EQ("Variable $a$ used but not defined.", error); 251 252 catalogs_[0].reset(CreateBadDictionary(CONTENT_MISSING)); 253 handler_.reset(MessageBundle::Create(catalogs_, &error)); 254 EXPECT_TRUE(handler_.get() == NULL); 255 EXPECT_EQ("Invalid \"content\" element for key n1.", error); 256 257 catalogs_[0].reset(CreateBadDictionary(MESSAGE_PLACEHOLDER_DOESNT_MATCH)); 258 handler_.reset(MessageBundle::Create(catalogs_, &error)); 259 EXPECT_TRUE(handler_.get() == NULL); 260 EXPECT_EQ("Variable $a$ used but not defined.", error); 261 } 262 263 TEST_F(MessageBundleTest, ReservedMessagesOverrideDeveloperMessages) { 264 catalogs_.push_back( 265 linked_ptr<base::DictionaryValue>(CreateGoodDictionary())); 266 267 base::DictionaryValue* dict = catalogs_[0].get(); 268 CreateMessageTree(MessageBundle::kUILocaleKey, "x", false, dict); 269 270 std::string error = CreateMessageBundle(); 271 272 EXPECT_TRUE(handler_.get() == NULL); 273 std::string expected_error = ErrorUtils::FormatErrorMessage( 274 errors::kReservedMessageFound, MessageBundle::kUILocaleKey); 275 EXPECT_EQ(expected_error, error); 276 } 277 278 TEST_F(MessageBundleTest, AppendReservedMessagesForLTR) { 279 CreateMessageBundle(); 280 281 ASSERT_TRUE(handler_.get() != NULL); 282 ClearDictionary(); 283 ASSERT_TRUE(AppendReservedMessages("en_US")); 284 285 EXPECT_EQ("en_US", 286 handler_->GetL10nMessage(MessageBundle::kUILocaleKey)); 287 EXPECT_EQ("ltr", handler_->GetL10nMessage( 288 MessageBundle::kBidiDirectionKey)); 289 EXPECT_EQ("rtl", handler_->GetL10nMessage( 290 MessageBundle::kBidiReversedDirectionKey)); 291 EXPECT_EQ("left", handler_->GetL10nMessage( 292 MessageBundle::kBidiStartEdgeKey)); 293 EXPECT_EQ("right", handler_->GetL10nMessage( 294 MessageBundle::kBidiEndEdgeKey)); 295 } 296 297 TEST_F(MessageBundleTest, AppendReservedMessagesForRTL) { 298 CreateMessageBundle(); 299 300 ASSERT_TRUE(handler_.get() != NULL); 301 ClearDictionary(); 302 ASSERT_TRUE(AppendReservedMessages("he")); 303 304 EXPECT_EQ("he", 305 handler_->GetL10nMessage(MessageBundle::kUILocaleKey)); 306 EXPECT_EQ("rtl", handler_->GetL10nMessage( 307 MessageBundle::kBidiDirectionKey)); 308 EXPECT_EQ("ltr", handler_->GetL10nMessage( 309 MessageBundle::kBidiReversedDirectionKey)); 310 EXPECT_EQ("right", handler_->GetL10nMessage( 311 MessageBundle::kBidiStartEdgeKey)); 312 EXPECT_EQ("left", handler_->GetL10nMessage( 313 MessageBundle::kBidiEndEdgeKey)); 314 } 315 316 TEST_F(MessageBundleTest, IsValidNameCheckValidCharacters) { 317 EXPECT_TRUE(MessageBundle::IsValidName(std::string("a__BV_9"))); 318 EXPECT_TRUE(MessageBundle::IsValidName(std::string("@@a__BV_9"))); 319 EXPECT_FALSE(MessageBundle::IsValidName(std::string("$a__BV_9$"))); 320 EXPECT_FALSE(MessageBundle::IsValidName(std::string("a-BV-9"))); 321 EXPECT_FALSE(MessageBundle::IsValidName(std::string("a#BV!9"))); 322 EXPECT_FALSE(MessageBundle::IsValidName(std::string("a<b"))); 323 } 324 325 struct ReplaceVariables { 326 const char* original; 327 const char* result; 328 const char* error; 329 const char* begin_delimiter; 330 const char* end_delimiter; 331 bool pass; 332 }; 333 334 TEST(MessageBundle, ReplaceMessagesInText) { 335 const char* kMessageBegin = MessageBundle::kMessageBegin; 336 const char* kMessageEnd = MessageBundle::kMessageEnd; 337 const char* kPlaceholderBegin = MessageBundle::kPlaceholderBegin; 338 const char* kPlaceholderEnd = MessageBundle::kPlaceholderEnd; 339 340 static ReplaceVariables test_cases[] = { 341 // Message replacement. 342 { "This is __MSG_siMPle__ message", "This is simple message", 343 "", kMessageBegin, kMessageEnd, true }, 344 { "This is __MSG_", "This is __MSG_", 345 "", kMessageBegin, kMessageEnd, true }, 346 { "This is __MSG__simple__ message", "This is __MSG__simple__ message", 347 "Variable __MSG__simple__ used but not defined.", 348 kMessageBegin, kMessageEnd, false }, 349 { "__MSG_LoNg__", "A pretty long replacement", 350 "", kMessageBegin, kMessageEnd, true }, 351 { "A __MSG_SimpLE__MSG_ a", "A simpleMSG_ a", 352 "", kMessageBegin, kMessageEnd, true }, 353 { "A __MSG_simple__MSG_long__", "A simpleMSG_long__", 354 "", kMessageBegin, kMessageEnd, true }, 355 { "A __MSG_simple____MSG_long__", "A simpleA pretty long replacement", 356 "", kMessageBegin, kMessageEnd, true }, 357 { "__MSG_d1g1ts_are_ok__", "I are d1g1t", 358 "", kMessageBegin, kMessageEnd, true }, 359 // Placeholder replacement. 360 { "This is $sImpLe$ message", "This is simple message", 361 "", kPlaceholderBegin, kPlaceholderEnd, true }, 362 { "This is $", "This is $", 363 "", kPlaceholderBegin, kPlaceholderEnd, true }, 364 { "This is $$sIMPle$ message", "This is $simple message", 365 "", kPlaceholderBegin, kPlaceholderEnd, true }, 366 { "$LONG_V$", "A pretty long replacement", 367 "", kPlaceholderBegin, kPlaceholderEnd, true }, 368 { "A $simple$$ a", "A simple$ a", 369 "", kPlaceholderBegin, kPlaceholderEnd, true }, 370 { "A $simple$long_v$", "A simplelong_v$", 371 "", kPlaceholderBegin, kPlaceholderEnd, true }, 372 { "A $simple$$long_v$", "A simpleA pretty long replacement", 373 "", kPlaceholderBegin, kPlaceholderEnd, true }, 374 { "This is $bad name$", "This is $bad name$", 375 "", kPlaceholderBegin, kPlaceholderEnd, true }, 376 { "This is $missing$", "This is $missing$", 377 "Variable $missing$ used but not defined.", 378 kPlaceholderBegin, kPlaceholderEnd, false }, 379 }; 380 381 MessageBundle::SubstitutionMap messages; 382 messages.insert(std::make_pair("simple", "simple")); 383 messages.insert(std::make_pair("long", "A pretty long replacement")); 384 messages.insert(std::make_pair("long_v", "A pretty long replacement")); 385 messages.insert(std::make_pair("bad name", "Doesn't matter")); 386 messages.insert(std::make_pair("d1g1ts_are_ok", "I are d1g1t")); 387 388 for (size_t i = 0; i < arraysize(test_cases); ++i) { 389 std::string text = test_cases[i].original; 390 std::string error; 391 EXPECT_EQ(test_cases[i].pass, 392 MessageBundle::ReplaceVariables(messages, 393 test_cases[i].begin_delimiter, 394 test_cases[i].end_delimiter, 395 &text, 396 &error)); 397 EXPECT_EQ(test_cases[i].result, text); 398 } 399 } 400 401 /////////////////////////////////////////////////////////////////////////////// 402 // 403 // Renderer helper functions test. 404 // 405 /////////////////////////////////////////////////////////////////////////////// 406 407 TEST(GetExtensionToL10nMessagesMapTest, ReturnsTheSameObject) { 408 ExtensionToL10nMessagesMap* map1 = GetExtensionToL10nMessagesMap(); 409 ASSERT_TRUE(NULL != map1); 410 411 ExtensionToL10nMessagesMap* map2 = GetExtensionToL10nMessagesMap(); 412 ASSERT_EQ(map1, map2); 413 } 414 415 TEST(GetExtensionToL10nMessagesMapTest, ReturnsNullForUnknownExtensionId) { 416 const std::string extension_id("some_unique_12334212314234_id"); 417 L10nMessagesMap* map = GetL10nMessagesMap(extension_id); 418 EXPECT_TRUE(NULL == map); 419 } 420 421 TEST(GetExtensionToL10nMessagesMapTest, ReturnsMapForKnownExtensionId) { 422 const std::string extension_id("some_unique_121212121212121_id"); 423 // Store a map for given id. 424 L10nMessagesMap messages; 425 messages.insert(std::make_pair("message_name", "message_value")); 426 (*GetExtensionToL10nMessagesMap())[extension_id] = messages; 427 428 L10nMessagesMap* map = GetL10nMessagesMap(extension_id); 429 ASSERT_TRUE(NULL != map); 430 EXPECT_EQ(1U, map->size()); 431 EXPECT_EQ("message_value", (*map)["message_name"]); 432 } 433 434 } // namespace extensions 435