Make ValuesTest use std::strings (instead of wstrings) for dictionary keys.

For now, keep the deprecated tests to check compatibility.

BUG=23581
TEST=base_unittests

Review URL: http://codereview.chromium.org/3041038

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@54431 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/base/values.h b/base/values.h
index cc9d0fc..2fc2044a 100644
--- a/base/values.h
+++ b/base/values.h
@@ -2,21 +2,21 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// This file specifies a recursive data storage class called Value
-// intended for storing setting and other persistable data.
-// It includes the ability to specify (recursive) lists and dictionaries, so
-// it's fairly expressive.  However, the API is optimized for the common case,
-// namely storing a hierarchical tree of simple values.  Given a
-// DictionaryValue root, you can easily do things like:
+// This file specifies a recursive data storage class called Value intended for
+// storing setting and other persistable data.  It includes the ability to
+// specify (recursive) lists and dictionaries, so it's fairly expressive.
+// However, the API is optimized for the common case, namely storing a
+// hierarchical tree of simple values.  Given a DictionaryValue root, you can
+// easily do things like:
 //
-// root->SetString(L"global.pages.homepage", L"http://goateleporter.com");
-// std::wstring homepage = L"http://google.com";  // default/fallback value
-// root->GetString(L"global.pages.homepage", &homepage);
+// root->SetString("global.pages.homepage", "http://goateleporter.com");
+// std::string homepage = "http://google.com";  // default/fallback value
+// root->GetString("global.pages.homepage", &homepage);
 //
-// where "global" and "pages" are also DictionaryValues, and "homepage"
-// is a string setting.  If some elements of the path didn't exist yet,
-// the SetString() method would create the missing elements and attach them
-// to root before attaching the homepage value.
+// where "global" and "pages" are also DictionaryValues, and "homepage" is a
+// string setting.  If some elements of the path didn't exist yet, the
+// SetString() method would create the missing elements and attach them to root
+// before attaching the homepage value.
 
 #ifndef BASE_VALUES_H_
 #define BASE_VALUES_H_
@@ -202,6 +202,9 @@
   DISALLOW_COPY_AND_ASSIGN(BinaryValue);
 };
 
+// DictionaryValue provides a key-value dictionary with (optional) "path"
+// parsing for recursive access; see the comment at the top of the file. Keys
+// are |std::string|s and should be UTF-8 encoded.
 // TODO(viettrungluu): Things marked DEPRECATED will be removed. crbug.com/23581
 class DictionaryValue : public Value {
  public:
diff --git a/base/values_unittest.cc b/base/values_unittest.cc
index 8aa8359..71eec5e2 100644
--- a/base/values_unittest.cc
+++ b/base/values_unittest.cc
@@ -13,9 +13,59 @@
 class ValuesTest: public testing::Test {
 };
 
+// TODO(viettrungluu): I changed the keys for DictionaryValue from std::wstring
+// to std::string. I've temporarily kept the old methods taking std::wstring for
+// compatibility. The ...Deprecated tests are the old tests which use these
+// methods, and remain to test compatibility. They will be removed once the old
+// methods are removed.
+
 TEST(ValuesTest, Basic) {
   // Test basic dictionary getting/setting
   DictionaryValue settings;
+  std::string homepage = "http://google.com";
+  ASSERT_FALSE(settings.GetString("global.homepage", &homepage));
+  ASSERT_EQ(std::string("http://google.com"), homepage);
+
+  ASSERT_FALSE(settings.Get("global", NULL));
+  settings.Set("global", Value::CreateBooleanValue(true));
+  ASSERT_TRUE(settings.Get("global", NULL));
+  settings.SetString("global.homepage", "http://scurvy.com");
+  ASSERT_TRUE(settings.Get("global", NULL));
+  homepage = "http://google.com";
+  ASSERT_TRUE(settings.GetString("global.homepage", &homepage));
+  ASSERT_EQ(std::string("http://scurvy.com"), homepage);
+
+  // Test storing a dictionary in a list.
+  ListValue* toolbar_bookmarks;
+  ASSERT_FALSE(
+    settings.GetList("global.toolbar.bookmarks", &toolbar_bookmarks));
+
+  toolbar_bookmarks = new ListValue;
+  settings.Set("global.toolbar.bookmarks", toolbar_bookmarks);
+  ASSERT_TRUE(settings.GetList("global.toolbar.bookmarks", &toolbar_bookmarks));
+
+  DictionaryValue* new_bookmark = new DictionaryValue;
+  new_bookmark->SetString("name", "Froogle");
+  new_bookmark->SetString("url", "http://froogle.com");
+  toolbar_bookmarks->Append(new_bookmark);
+
+  ListValue* bookmark_list;
+  ASSERT_TRUE(settings.GetList("global.toolbar.bookmarks", &bookmark_list));
+  DictionaryValue* bookmark;
+  ASSERT_EQ(1U, bookmark_list->GetSize());
+  ASSERT_TRUE(bookmark_list->GetDictionary(0, &bookmark));
+  std::string bookmark_name = "Unnamed";
+  ASSERT_TRUE(bookmark->GetString("name", &bookmark_name));
+  ASSERT_EQ(std::string("Froogle"), bookmark_name);
+  std::string bookmark_url;
+  ASSERT_TRUE(bookmark->GetString("url", &bookmark_url));
+  ASSERT_EQ(std::string("http://froogle.com"), bookmark_url);
+}
+
+// TODO(viettrungluu): deprecate:
+TEST(ValuesTest, BasicDeprecated) {
+  // Test basic dictionary getting/setting
+  DictionaryValue settings;
   std::wstring homepage = L"http://google.com";
   ASSERT_FALSE(
     settings.GetString(L"global.homepage", &homepage));
@@ -256,6 +306,35 @@
 }
 
 TEST(ValuesTest, DictionaryDeletion) {
+  std::string key = "test";
+  bool deletion_flag = true;
+
+  {
+    DictionaryValue dict;
+    dict.Set(key, new DeletionTestValue(&deletion_flag));
+    EXPECT_FALSE(deletion_flag);
+  }
+  EXPECT_TRUE(deletion_flag);
+
+  {
+    DictionaryValue dict;
+    dict.Set(key, new DeletionTestValue(&deletion_flag));
+    EXPECT_FALSE(deletion_flag);
+    dict.Clear();
+    EXPECT_TRUE(deletion_flag);
+  }
+
+  {
+    DictionaryValue dict;
+    dict.Set(key, new DeletionTestValue(&deletion_flag));
+    EXPECT_FALSE(deletion_flag);
+    dict.Set(key, Value::CreateNullValue());
+    EXPECT_TRUE(deletion_flag);
+  }
+}
+
+// TODO(viettrungluu): deprecate:
+TEST(ValuesTest, DictionaryDeletionDeprecated) {
   std::wstring key = L"test";
   bool deletion_flag = true;
 
@@ -284,6 +363,38 @@
 }
 
 TEST(ValuesTest, DictionaryRemoval) {
+  std::string key = "test";
+  bool deletion_flag = true;
+  Value* removed_item = NULL;
+
+  {
+    DictionaryValue dict;
+    dict.Set(key, new DeletionTestValue(&deletion_flag));
+    EXPECT_FALSE(deletion_flag);
+    EXPECT_TRUE(dict.HasKey(key));
+    EXPECT_FALSE(dict.Remove("absent key", &removed_item));
+    EXPECT_TRUE(dict.Remove(key, &removed_item));
+    EXPECT_FALSE(dict.HasKey(key));
+    ASSERT_TRUE(removed_item);
+  }
+  EXPECT_FALSE(deletion_flag);
+  delete removed_item;
+  removed_item = NULL;
+  EXPECT_TRUE(deletion_flag);
+
+  {
+    DictionaryValue dict;
+    dict.Set(key, new DeletionTestValue(&deletion_flag));
+    EXPECT_FALSE(deletion_flag);
+    EXPECT_TRUE(dict.HasKey(key));
+    EXPECT_TRUE(dict.Remove(key, NULL));
+    EXPECT_TRUE(deletion_flag);
+    EXPECT_FALSE(dict.HasKey(key));
+  }
+}
+
+// TODO(viettrungluu): deprecate:
+TEST(ValuesTest, DictionaryRemovalDeprecated) {
   std::wstring key = L"test";
   bool deletion_flag = true;
   Value* removed_item = NULL;
@@ -316,6 +427,29 @@
 
 TEST(ValuesTest, DictionaryWithoutPathExpansion) {
   DictionaryValue dict;
+  dict.Set("this.is.expanded", Value::CreateNullValue());
+  dict.SetWithoutPathExpansion("this.isnt.expanded", Value::CreateNullValue());
+
+  EXPECT_FALSE(dict.HasKey("this.is.expanded"));
+  EXPECT_TRUE(dict.HasKey("this"));
+  Value* value1;
+  EXPECT_TRUE(dict.Get("this", &value1));
+  DictionaryValue* value2;
+  ASSERT_TRUE(dict.GetDictionaryWithoutPathExpansion("this", &value2));
+  EXPECT_EQ(value1, value2);
+  EXPECT_EQ(1U, value2->size());
+
+  EXPECT_TRUE(dict.HasKey("this.isnt.expanded"));
+  Value* value3;
+  EXPECT_FALSE(dict.Get("this.isnt.expanded", &value3));
+  Value* value4;
+  ASSERT_TRUE(dict.GetWithoutPathExpansion("this.isnt.expanded", &value4));
+  EXPECT_EQ(Value::TYPE_NULL, value4->GetType());
+}
+
+// TODO(viettrungluu): deprecate:
+TEST(ValuesTest, DictionaryWithoutPathExpansionDeprecated) {
+  DictionaryValue dict;
   dict.Set(L"this.is.expanded", Value::CreateNullValue());
   dict.SetWithoutPathExpansion(L"this.isnt.expanded", Value::CreateNullValue());
 
@@ -339,6 +473,152 @@
 TEST(ValuesTest, DeepCopy) {
   DictionaryValue original_dict;
   Value* original_null = Value::CreateNullValue();
+  original_dict.Set("null", original_null);
+  Value* original_bool = Value::CreateBooleanValue(true);
+  original_dict.Set("bool", original_bool);
+  Value* original_int = Value::CreateIntegerValue(42);
+  original_dict.Set("int", original_int);
+  Value* original_real = Value::CreateRealValue(3.14);
+  original_dict.Set("real", original_real);
+  Value* original_string = Value::CreateStringValue("hello");
+  original_dict.Set("string", original_string);
+  Value* original_wstring = Value::CreateStringValue(L"peek-a-boo");
+  original_dict.Set("wstring", original_wstring);
+  Value* original_utf16 =
+      Value::CreateStringValueFromUTF16(ASCIIToUTF16("hello16"));
+  original_dict.Set("utf16", original_utf16);
+
+  char* original_buffer = new char[42];
+  memset(original_buffer, '!', 42);
+  BinaryValue* original_binary = Value::CreateBinaryValue(original_buffer, 42);
+  original_dict.Set("binary", original_binary);
+
+  ListValue* original_list = new ListValue();
+  Value* original_list_element_0 = Value::CreateIntegerValue(0);
+  original_list->Append(original_list_element_0);
+  Value* original_list_element_1 = Value::CreateIntegerValue(1);
+  original_list->Append(original_list_element_1);
+  original_dict.Set("list", original_list);
+
+  scoped_ptr<DictionaryValue> copy_dict(
+      static_cast<DictionaryValue*>(original_dict.DeepCopy()));
+  ASSERT_TRUE(copy_dict.get());
+  ASSERT_NE(copy_dict.get(), &original_dict);
+
+  Value* copy_null = NULL;
+  ASSERT_TRUE(copy_dict->Get("null", &copy_null));
+  ASSERT_TRUE(copy_null);
+  ASSERT_NE(copy_null, original_null);
+  ASSERT_TRUE(copy_null->IsType(Value::TYPE_NULL));
+
+  Value* copy_bool = NULL;
+  ASSERT_TRUE(copy_dict->Get("bool", &copy_bool));
+  ASSERT_TRUE(copy_bool);
+  ASSERT_NE(copy_bool, original_bool);
+  ASSERT_TRUE(copy_bool->IsType(Value::TYPE_BOOLEAN));
+  bool copy_bool_value = false;
+  ASSERT_TRUE(copy_bool->GetAsBoolean(&copy_bool_value));
+  ASSERT_TRUE(copy_bool_value);
+
+  Value* copy_int = NULL;
+  ASSERT_TRUE(copy_dict->Get("int", &copy_int));
+  ASSERT_TRUE(copy_int);
+  ASSERT_NE(copy_int, original_int);
+  ASSERT_TRUE(copy_int->IsType(Value::TYPE_INTEGER));
+  int copy_int_value = 0;
+  ASSERT_TRUE(copy_int->GetAsInteger(&copy_int_value));
+  ASSERT_EQ(42, copy_int_value);
+
+  Value* copy_real = NULL;
+  ASSERT_TRUE(copy_dict->Get("real", &copy_real));
+  ASSERT_TRUE(copy_real);
+  ASSERT_NE(copy_real, original_real);
+  ASSERT_TRUE(copy_real->IsType(Value::TYPE_REAL));
+  double copy_real_value = 0;
+  ASSERT_TRUE(copy_real->GetAsReal(&copy_real_value));
+  ASSERT_EQ(3.14, copy_real_value);
+
+  Value* copy_string = NULL;
+  ASSERT_TRUE(copy_dict->Get("string", &copy_string));
+  ASSERT_TRUE(copy_string);
+  ASSERT_NE(copy_string, original_string);
+  ASSERT_TRUE(copy_string->IsType(Value::TYPE_STRING));
+  std::string copy_string_value;
+  std::wstring copy_wstring_value;
+  string16 copy_utf16_value;
+  ASSERT_TRUE(copy_string->GetAsString(&copy_string_value));
+  ASSERT_TRUE(copy_string->GetAsString(&copy_wstring_value));
+  ASSERT_TRUE(copy_string->GetAsUTF16(&copy_utf16_value));
+  ASSERT_EQ(std::string("hello"), copy_string_value);
+  ASSERT_EQ(std::wstring(L"hello"), copy_wstring_value);
+  ASSERT_EQ(ASCIIToUTF16("hello"), copy_utf16_value);
+
+  Value* copy_wstring = NULL;
+  ASSERT_TRUE(copy_dict->Get("wstring", &copy_wstring));
+  ASSERT_TRUE(copy_wstring);
+  ASSERT_NE(copy_wstring, original_wstring);
+  ASSERT_TRUE(copy_wstring->IsType(Value::TYPE_STRING));
+  ASSERT_TRUE(copy_wstring->GetAsString(&copy_string_value));
+  ASSERT_TRUE(copy_wstring->GetAsString(&copy_wstring_value));
+  ASSERT_TRUE(copy_wstring->GetAsUTF16(&copy_utf16_value));
+  ASSERT_EQ(std::string("peek-a-boo"), copy_string_value);
+  ASSERT_EQ(std::wstring(L"peek-a-boo"), copy_wstring_value);
+  ASSERT_EQ(ASCIIToUTF16("peek-a-boo"), copy_utf16_value);
+
+  Value* copy_utf16 = NULL;
+  ASSERT_TRUE(copy_dict->Get("utf16", &copy_utf16));
+  ASSERT_TRUE(copy_utf16);
+  ASSERT_NE(copy_utf16, original_utf16);
+  ASSERT_TRUE(copy_utf16->IsType(Value::TYPE_STRING));
+  ASSERT_TRUE(copy_utf16->GetAsString(&copy_string_value));
+  ASSERT_TRUE(copy_utf16->GetAsString(&copy_wstring_value));
+  ASSERT_TRUE(copy_utf16->GetAsUTF16(&copy_utf16_value));
+  ASSERT_EQ(std::string("hello16"), copy_string_value);
+  ASSERT_EQ(std::wstring(L"hello16"), copy_wstring_value);
+  ASSERT_EQ(ASCIIToUTF16("hello16"), copy_utf16_value);
+
+  Value* copy_binary = NULL;
+  ASSERT_TRUE(copy_dict->Get("binary", &copy_binary));
+  ASSERT_TRUE(copy_binary);
+  ASSERT_NE(copy_binary, original_binary);
+  ASSERT_TRUE(copy_binary->IsType(Value::TYPE_BINARY));
+  ASSERT_NE(original_binary->GetBuffer(),
+    static_cast<BinaryValue*>(copy_binary)->GetBuffer());
+  ASSERT_EQ(original_binary->GetSize(),
+    static_cast<BinaryValue*>(copy_binary)->GetSize());
+  ASSERT_EQ(0, memcmp(original_binary->GetBuffer(),
+               static_cast<BinaryValue*>(copy_binary)->GetBuffer(),
+               original_binary->GetSize()));
+
+  Value* copy_value = NULL;
+  ASSERT_TRUE(copy_dict->Get("list", &copy_value));
+  ASSERT_TRUE(copy_value);
+  ASSERT_NE(copy_value, original_list);
+  ASSERT_TRUE(copy_value->IsType(Value::TYPE_LIST));
+  ListValue* copy_list = static_cast<ListValue*>(copy_value);
+  ASSERT_EQ(2U, copy_list->GetSize());
+
+  Value* copy_list_element_0;
+  ASSERT_TRUE(copy_list->Get(0, &copy_list_element_0));
+  ASSERT_TRUE(copy_list_element_0);
+  ASSERT_NE(copy_list_element_0, original_list_element_0);
+  int copy_list_element_0_value;
+  ASSERT_TRUE(copy_list_element_0->GetAsInteger(&copy_list_element_0_value));
+  ASSERT_EQ(0, copy_list_element_0_value);
+
+  Value* copy_list_element_1;
+  ASSERT_TRUE(copy_list->Get(1, &copy_list_element_1));
+  ASSERT_TRUE(copy_list_element_1);
+  ASSERT_NE(copy_list_element_1, original_list_element_1);
+  int copy_list_element_1_value;
+  ASSERT_TRUE(copy_list_element_1->GetAsInteger(&copy_list_element_1_value));
+  ASSERT_EQ(1, copy_list_element_1_value);
+}
+
+// TODO(viettrungluu): deprecate:
+TEST(ValuesTest, DeepCopyDeprecated) {
+  DictionaryValue original_dict;
+  Value* original_null = Value::CreateNullValue();
   original_dict.Set(L"null", original_null);
   Value* original_bool = Value::CreateBooleanValue(true);
   original_dict.Set(L"bool", original_bool);
@@ -494,6 +774,44 @@
   delete boolean;
 
   DictionaryValue dv;
+  dv.SetBoolean("a", false);
+  dv.SetInteger("b", 2);
+  dv.SetReal("c", 2.5);
+  dv.SetString("d1", "string");
+  dv.SetStringFromUTF16("d2", ASCIIToUTF16("http://google.com"));
+  dv.Set("e", Value::CreateNullValue());
+
+  DictionaryValue* copy = static_cast<DictionaryValue*>(dv.DeepCopy());
+  EXPECT_TRUE(dv.Equals(copy));
+
+  ListValue* list = new ListValue;
+  list->Append(Value::CreateNullValue());
+  list->Append(new DictionaryValue);
+  dv.Set("f", list);
+
+  EXPECT_FALSE(dv.Equals(copy));
+  copy->Set("f", list->DeepCopy());
+  EXPECT_TRUE(dv.Equals(copy));
+
+  list->Append(Value::CreateBooleanValue(true));
+  EXPECT_FALSE(dv.Equals(copy));
+  delete copy;
+}
+
+// TODO(viettrungluu): deprecate:
+TEST(ValuesTest, EqualsDeprecated) {
+  Value* null1 = Value::CreateNullValue();
+  Value* null2 = Value::CreateNullValue();
+  EXPECT_NE(null1, null2);
+  EXPECT_TRUE(null1->Equals(null2));
+
+  Value* boolean = Value::CreateBooleanValue(false);
+  EXPECT_FALSE(null1->Equals(boolean));
+  delete null1;
+  delete null2;
+  delete boolean;
+
+  DictionaryValue dv;
   dv.SetBoolean(L"a", false);
   dv.SetInteger(L"b", 2);
   dv.SetReal(L"c", 2.5);
@@ -521,6 +839,82 @@
 TEST(ValuesTest, RemoveEmptyChildren) {
   scoped_ptr<DictionaryValue> root(new DictionaryValue);
   // Remove empty lists and dictionaries.
+  root->Set("empty_dict", new DictionaryValue);
+  root->Set("empty_list", new ListValue);
+  root->SetWithoutPathExpansion("a.b.c.d.e", new DictionaryValue);
+  root.reset(root->DeepCopyWithoutEmptyChildren());
+  EXPECT_TRUE(root->empty());
+
+  // Make sure we don't prune too much.
+  root->SetBoolean("bool", true);
+  root->Set("empty_dict", new DictionaryValue);
+  root->SetString("empty_string", "");
+  root.reset(root->DeepCopyWithoutEmptyChildren());
+  EXPECT_EQ(2U, root->size());
+
+  // Should do nothing.
+  root.reset(root->DeepCopyWithoutEmptyChildren());
+  EXPECT_EQ(2U, root->size());
+
+  // Nested test cases.  These should all reduce back to the bool and string
+  // set above.
+  {
+    root->Set("a.b.c.d.e", new DictionaryValue);
+    root.reset(root->DeepCopyWithoutEmptyChildren());
+    EXPECT_EQ(2U, root->size());
+  }
+  {
+    DictionaryValue* inner = new DictionaryValue;
+    root->Set("dict_with_emtpy_children", inner);
+    inner->Set("empty_dict", new DictionaryValue);
+    inner->Set("empty_list", new ListValue);
+    root.reset(root->DeepCopyWithoutEmptyChildren());
+    EXPECT_EQ(2U, root->size());
+  }
+  {
+    ListValue* inner = new ListValue;
+    root->Set("list_with_empty_children", inner);
+    inner->Append(new DictionaryValue);
+    inner->Append(new ListValue);
+    root.reset(root->DeepCopyWithoutEmptyChildren());
+    EXPECT_EQ(2U, root->size());
+  }
+
+  // Nested with siblings.
+  {
+    ListValue* inner = new ListValue;
+    root->Set("list_with_empty_children", inner);
+    inner->Append(new DictionaryValue);
+    inner->Append(new ListValue);
+    DictionaryValue* inner2 = new DictionaryValue;
+    root->Set("dict_with_empty_children", inner2);
+    inner2->Set("empty_dict", new DictionaryValue);
+    inner2->Set("empty_list", new ListValue);
+    root.reset(root->DeepCopyWithoutEmptyChildren());
+    EXPECT_EQ(2U, root->size());
+  }
+
+  // Make sure nested values don't get pruned.
+  {
+    ListValue* inner = new ListValue;
+    root->Set("list_with_empty_children", inner);
+    ListValue* inner2 = new ListValue;
+    inner->Append(new DictionaryValue);
+    inner->Append(inner2);
+    inner2->Append(Value::CreateStringValue("hello"));
+    root.reset(root->DeepCopyWithoutEmptyChildren());
+    EXPECT_EQ(3U, root->size());
+    EXPECT_TRUE(root->GetList("list_with_empty_children", &inner));
+    EXPECT_EQ(1U, inner->GetSize());  // Dictionary was pruned.
+    EXPECT_TRUE(inner->GetList(0, &inner2));
+    EXPECT_EQ(1U, inner2->GetSize());
+  }
+}
+
+// TODO(viettrungluu): deprecate:
+TEST(ValuesTest, RemoveEmptyChildrenDeprecated) {
+  scoped_ptr<DictionaryValue> root(new DictionaryValue);
+  // Remove empty lists and dictionaries.
   root->Set(L"empty_dict", new DictionaryValue);
   root->Set(L"empty_list", new ListValue);
   root->SetWithoutPathExpansion(L"a.b.c.d.e", new DictionaryValue);
@@ -595,6 +989,52 @@
 
 TEST(ValuesTest, MergeDictionary) {
   scoped_ptr<DictionaryValue> base(new DictionaryValue);
+  base->SetString("base_key", "base_key_value_base");
+  base->SetString("collide_key", "collide_key_value_base");
+  DictionaryValue* base_sub_dict = new DictionaryValue;
+  base_sub_dict->SetString("sub_base_key", "sub_base_key_value_base");
+  base_sub_dict->SetString("sub_collide_key", "sub_collide_key_value_base");
+  base->Set("sub_dict_key", base_sub_dict);
+
+  scoped_ptr<DictionaryValue> merge(new DictionaryValue);
+  merge->SetString("merge_key", "merge_key_value_merge");
+  merge->SetString("collide_key", "collide_key_value_merge");
+  DictionaryValue* merge_sub_dict = new DictionaryValue;
+  merge_sub_dict->SetString("sub_merge_key", "sub_merge_key_value_merge");
+  merge_sub_dict->SetString("sub_collide_key", "sub_collide_key_value_merge");
+  merge->Set("sub_dict_key", merge_sub_dict);
+
+  base->MergeDictionary(merge.get());
+
+  EXPECT_EQ(4U, base->size());
+  std::string base_key_value;
+  EXPECT_TRUE(base->GetString("base_key", &base_key_value));
+  EXPECT_EQ("base_key_value_base", base_key_value); // Base value preserved.
+  std::string collide_key_value;
+  EXPECT_TRUE(base->GetString("collide_key", &collide_key_value));
+  EXPECT_EQ("collide_key_value_merge", collide_key_value); // Replaced.
+  std::string merge_key_value;
+  EXPECT_TRUE(base->GetString("merge_key", &merge_key_value));
+  EXPECT_EQ("merge_key_value_merge", merge_key_value); // Merged in.
+
+  DictionaryValue* res_sub_dict;
+  EXPECT_TRUE(base->GetDictionary("sub_dict_key", &res_sub_dict));
+  EXPECT_EQ(3U, res_sub_dict->size());
+  std::string sub_base_key_value;
+  EXPECT_TRUE(res_sub_dict->GetString("sub_base_key", &sub_base_key_value));
+  EXPECT_EQ("sub_base_key_value_base", sub_base_key_value); // Preserved.
+  std::string sub_collide_key_value;
+  EXPECT_TRUE(res_sub_dict->GetString("sub_collide_key",
+                                      &sub_collide_key_value));
+  EXPECT_EQ("sub_collide_key_value_merge", sub_collide_key_value); // Replaced.
+  std::string sub_merge_key_value;
+  EXPECT_TRUE(res_sub_dict->GetString("sub_merge_key", &sub_merge_key_value));
+  EXPECT_EQ("sub_merge_key_value_merge", sub_merge_key_value); // Merged in.
+}
+
+// TODO(viettrungluu): deprecate:
+TEST(ValuesTest, MergeDictionaryDeprecated) {
+  scoped_ptr<DictionaryValue> base(new DictionaryValue);
   base->SetString(L"base_key", "base_key_value_base");
   base->SetString(L"collide_key", "collide_key_value_base");
   DictionaryValue* base_sub_dict = new DictionaryValue;