| OLD | NEW |
| (Empty) |
| 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 | |
| OLD | NEW |