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