1 // Copyright 2014 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.
5 #include "extensions/common/message_bundle.h"
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 "extensions/common/error_utils.h"
17 #include "extensions/common/extension_l10n_util.h"
18 #include "extensions/common/manifest_constants.h"
19 #include "testing/gtest/include/gtest/gtest.h"
21 namespace extensions
{
23 namespace errors
= manifest_errors
;
25 class MessageBundleTest
: public testing::Test
{
32 PLACEHOLDER_NOT_A_TREE
,
33 EMPTY_PLACEHOLDER_TREE
,
35 MESSAGE_PLACEHOLDER_DOESNT_MATCH
,
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
));
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
);
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
,
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
);
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
);
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
) {
89 CreateMessageTree("n 5", "nevermind", false, dict
);
92 dict
->SetString("n4", "whatever");
94 case EMPTY_NAME_TREE
: {
95 base::DictionaryValue
* empty_tree
= new base::DictionaryValue
;
96 SetDictionary("n4", empty_tree
, dict
);
100 dict
->Remove("n1.message", NULL
);
102 case PLACEHOLDER_NOT_A_TREE
:
103 dict
->SetString("n1.placeholders", "whatever");
105 case EMPTY_PLACEHOLDER_TREE
: {
106 base::DictionaryValue
* empty_tree
= new base::DictionaryValue
;
107 SetDictionary("n1.placeholders", empty_tree
, dict
);
110 case CONTENT_MISSING
:
111 dict
->Remove("n1.placeholders.a.content", NULL
);
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
);
124 unsigned int ReservedMessagesCount() {
125 // Update when adding new reserved messages.
129 void CheckReservedMessages(MessageBundle
* handler
) {
130 std::string ui_locale
= extension_l10n_util::CurrentLocaleOrDefault();
132 handler
->GetL10nMessage(MessageBundle::kUILocaleKey
));
134 std::string text_dir
= "ltr";
135 if (base::i18n::GetTextDirectionForLocale(ui_locale
.c_str()) ==
136 base::i18n::RIGHT_TO_LEFT
)
139 EXPECT_EQ(text_dir
, handler
->GetL10nMessage(
140 MessageBundle::kBidiDirectionKey
));
143 bool AppendReservedMessages(const std::string
& application_locale
) {
145 return handler_
->AppendReservedMessagesForLocale(
146 application_locale
, &error
);
149 std::string
CreateMessageBundle() {
151 handler_
.reset(MessageBundle::Create(catalogs_
, &error
));
156 void ClearDictionary() {
157 handler_
->dictionary_
.clear();
160 scoped_ptr
<MessageBundle
> handler_
;
161 std::vector
<linked_ptr
<base::DictionaryValue
> > catalogs_
;
164 TEST_F(MessageBundleTest
, ReservedMessagesCount
) {
165 ASSERT_EQ(5U, ReservedMessagesCount());
168 TEST_F(MessageBundleTest
, InitEmptyDictionaries
) {
169 CreateMessageBundle();
170 EXPECT_TRUE(handler_
.get() != NULL
);
171 EXPECT_EQ(0U + ReservedMessagesCount(), handler_
->size());
172 CheckReservedMessages(handler_
.get());
175 TEST_F(MessageBundleTest
, InitGoodDefaultDict
) {
177 linked_ptr
<base::DictionaryValue
>(CreateGoodDictionary()));
178 CreateMessageBundle();
180 EXPECT_TRUE(handler_
.get() != NULL
);
181 EXPECT_EQ(3U + ReservedMessagesCount(), handler_
->size());
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());
189 TEST_F(MessageBundleTest
, InitAppDictConsultedFirst
) {
191 linked_ptr
<base::DictionaryValue
>(CreateGoodDictionary()));
193 linked_ptr
<base::DictionaryValue
>(CreateGoodDictionary()));
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
);
204 CreateMessageBundle();
206 EXPECT_TRUE(handler_
.get() != NULL
);
207 EXPECT_EQ(3U + ReservedMessagesCount(), handler_
->size());
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());
215 TEST_F(MessageBundleTest
, InitBadAppDict
) {
217 linked_ptr
<base::DictionaryValue
>(CreateBadDictionary(INVALID_NAME
)));
219 linked_ptr
<base::DictionaryValue
>(CreateGoodDictionary()));
221 std::string error
= CreateMessageBundle();
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
);
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
);
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
);
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
);
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
);
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
);
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
);
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
);
263 TEST_F(MessageBundleTest
, ReservedMessagesOverrideDeveloperMessages
) {
265 linked_ptr
<base::DictionaryValue
>(CreateGoodDictionary()));
267 base::DictionaryValue
* dict
= catalogs_
[0].get();
268 CreateMessageTree(MessageBundle::kUILocaleKey
, "x", false, dict
);
270 std::string error
= CreateMessageBundle();
272 EXPECT_TRUE(handler_
.get() == NULL
);
273 std::string expected_error
= ErrorUtils::FormatErrorMessage(
274 errors::kReservedMessageFound
, MessageBundle::kUILocaleKey
);
275 EXPECT_EQ(expected_error
, error
);
278 TEST_F(MessageBundleTest
, AppendReservedMessagesForLTR
) {
279 CreateMessageBundle();
281 ASSERT_TRUE(handler_
.get() != NULL
);
283 ASSERT_TRUE(AppendReservedMessages("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
));
297 TEST_F(MessageBundleTest
, AppendReservedMessagesForRTL
) {
298 CreateMessageBundle();
300 ASSERT_TRUE(handler_
.get() != NULL
);
302 ASSERT_TRUE(AppendReservedMessages("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
));
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")));
325 struct ReplaceVariables
{
326 const char* original
;
329 const char* begin_delimiter
;
330 const char* end_delimiter
;
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
;
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 },
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"));
388 for (size_t i
= 0; i
< arraysize(test_cases
); ++i
) {
389 std::string text
= test_cases
[i
].original
;
391 EXPECT_EQ(test_cases
[i
].pass
,
392 MessageBundle::ReplaceVariables(messages
,
393 test_cases
[i
].begin_delimiter
,
394 test_cases
[i
].end_delimiter
,
397 EXPECT_EQ(test_cases
[i
].result
, text
);
401 ///////////////////////////////////////////////////////////////////////////////
403 // Renderer helper functions test.
405 ///////////////////////////////////////////////////////////////////////////////
407 TEST(GetExtensionToL10nMessagesMapTest
, ReturnsTheSameObject
) {
408 ExtensionToL10nMessagesMap
* map1
= GetExtensionToL10nMessagesMap();
409 ASSERT_TRUE(NULL
!= map1
);
411 ExtensionToL10nMessagesMap
* map2
= GetExtensionToL10nMessagesMap();
412 ASSERT_EQ(map1
, map2
);
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
);
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
;
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"]);
434 } // namespace extensions