diff --git a/Local.testsettings b/Local.testsettings
new file mode 100644
index 0000000..9654e6a
--- /dev/null
+++ b/Local.testsettings
@@ -0,0 +1,10 @@
+
+
+ These are default test settings for a local test run.
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TraceAndTestImpact.testsettings b/TraceAndTestImpact.testsettings
new file mode 100644
index 0000000..88f9753
--- /dev/null
+++ b/TraceAndTestImpact.testsettings
@@ -0,0 +1,21 @@
+
+
+ These are test settings for Trace and Test Impact.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mustache-sharp.sln b/mustache-sharp.sln
new file mode 100644
index 0000000..98d40ad
--- /dev/null
+++ b/mustache-sharp.sln
@@ -0,0 +1,36 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual Studio 2010
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mustache-sharp", "mustache-sharp\mustache-sharp.csproj", "{D71B378F-A4BA-4263-A4F0-07A49A0C528D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mustache-sharp.test", "mustache-sharp.test\mustache-sharp.test.csproj", "{7F607362-0680-4751-B1DC-621219294AE3}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{25414E49-67E6-4B8D-8AD8-78C70F8992A7}"
+ ProjectSection(SolutionItems) = preProject
+ Local.testsettings = Local.testsettings
+ mustache-sharp.vsmdi = mustache-sharp.vsmdi
+ TraceAndTestImpact.testsettings = TraceAndTestImpact.testsettings
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(TestCaseManagementSettings) = postSolution
+ CategoryFile = mustache-sharp.vsmdi
+ EndGlobalSection
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {D71B378F-A4BA-4263-A4F0-07A49A0C528D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D71B378F-A4BA-4263-A4F0-07A49A0C528D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D71B378F-A4BA-4263-A4F0-07A49A0C528D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D71B378F-A4BA-4263-A4F0-07A49A0C528D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7F607362-0680-4751-B1DC-621219294AE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7F607362-0680-4751-B1DC-621219294AE3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7F607362-0680-4751-B1DC-621219294AE3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7F607362-0680-4751-B1DC-621219294AE3}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/mustache-sharp.test/FormatterTester.cs b/mustache-sharp.test/FormatterTester.cs
new file mode 100644
index 0000000..52d236e
--- /dev/null
+++ b/mustache-sharp.test/FormatterTester.cs
@@ -0,0 +1,524 @@
+using System;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Collections.Generic;
+using System.Globalization;
+
+namespace mustache.test
+{
+ ///
+ /// Tests the Formatter class.
+ ///
+ [TestClass]
+ public class FormatterTester
+ {
+ #region Real World Example
+
+ ///
+ /// The Formatter class is especially useful when performing simple mail merge operations.
+ /// Like String.Format, Formatter will substitute placeholders for actual values. In the case
+ /// of Formatter, placeholders are indicated by name, rather than index and are wrapped with
+ /// double curly braces: {{name}}. The name within the curly brace can include any characters,
+ /// including whitespace, except for two or more adjacent right curly braces (}}).
+ ///
+ [TestMethod]
+ public void TestFormatter_ReplaceNamedPlaceholdersWithFormats()
+ {
+ const string format = "Hello {{name}}! It is {{date:MM-dd-yyyy}}. You make {{income:C}} an hour.";
+ Formatter formatter = new Formatter(format);
+ string result1 = formatter.Format(new Dictionary()
+ {
+ { "name", "Bob" },
+ { "date", new DateTime(2012, 03, 11) },
+ { "income", 32.8 }
+ });
+ Assert.AreEqual("Hello Bob! It is 03-11-2012. You make $32.80 an hour.", result1);
+ }
+
+ ///
+ /// If we want to work with objects, rather than raw dictionaries, we can wrap the objects with
+ /// property dictionaries.
+ ///
+ [TestMethod]
+ public void TestFormatter_UseObject()
+ {
+ var person = new
+ {
+ Name = "Bob",
+ Date = new DateTime(2012, 03, 11),
+ Income = 32.8
+ };
+ const string format = "Hello {{Name}}! It is {{Date:MM-dd-yyyy}}. You make {{Income:C}} an hour.";
+ Formatter formatter = new Formatter(format);
+ string result1 = formatter.Format(person);
+ Assert.AreEqual("Hello Bob! It is 03-11-2012. You make $32.80 an hour.", result1);
+ }
+
+ ///
+ /// We can the Formatter to print out a list of items following a format.
+ ///
+ [TestMethod]
+ public void TestFormatter_PrintList()
+ {
+ List values = new List() { 0, 1, 2, 3, 4 };
+ const string format = "{{#each this}}{{this}} {{/each}}";
+ Formatter formatter = new Formatter(format);
+ string result = formatter.Format(values);
+ Assert.AreEqual("0 1 2 3 4 ", result);
+ }
+
+ ///
+ /// We can include some text conditionally.
+ ///
+ [TestMethod]
+ public void TestFormatter_ConditionallyIncludeText()
+ {
+ Random random = new Random();
+ int value = random.Next();
+ bool isEven = value % 2 == 0;
+ var data = new
+ {
+ Value = value,
+ IsEven = isEven,
+ };
+ const string format = "{{Value}} {{#if IsEven}}is even{{#else}}is odd{{/if}}.";
+ Formatter formatter = new Formatter(format);
+ string result = formatter.Format(data);
+ string expected = String.Format("{0}", value) + (isEven ? " is even." : " is odd.");
+ Assert.AreEqual(expected, result);
+ }
+
+ ///
+ /// Multiple cases can be handled using if/elif/else.
+ ///
+ [TestMethod]
+ public void TestFormatter_HandleCases()
+ {
+ const string format = @"{{#if No}}No{{#elif Yes}}Yes{{#else}}Maybe{{/if}}";
+ Formatter formatter = new Formatter(format);
+ var data = new
+ {
+ Yes = true,
+ No = false,
+ };
+ string result = formatter.Format(data);
+ Assert.AreEqual("Yes", result);
+ }
+
+ ///
+ /// We should be able to combine tags anyway we want.
+ ///
+ [TestMethod]
+ public void TestFormatter_Compound()
+ {
+ const string format = @"{{#with Customer}}
+Hello{{#if FirstName}} {{FirstName}}{{/if}}:
+{{/with}}
+{{#! We only want to print out purchases if they have some. }}
+{{#if Purchases}}
+
+You recently purchased:
+{{#each Purchases}}
+ {{Name}}: {{Quantity}} x {{Price:C}}
+{{/each}}
+Your total was: {{Total:C}}
+{{/if}}
+
+We thought you might be interested in buying: {{PromotionProduct}}.
+
+Thank you,
+{{#with Agent}}
+{{Name}}
+{{/with}}";
+
+ Formatter formatter = new Formatter(format);
+ var data = new
+ {
+ Customer = new
+ {
+ FirstName = "Bob",
+ },
+ Purchases = new object[]
+ {
+ new
+ {
+ Name = "Donkey",
+ Quantity = 8,
+ Price = 1.23m,
+ },
+ new
+ {
+ Name = "Hammer",
+ Quantity = 1,
+ Price = 8.32m,
+ },
+ },
+ Total = 18.16m,
+ PromotionProduct = "Sneakers",
+ Agent = new
+ {
+ Name = "Tom",
+ },
+ };
+ string result = formatter.Format(data);
+ Assert.AreEqual(@"Hello Bob:
+
+You recently purchased:
+ Donkey: 8 x $1.23
+ Hammer: 1 x $8.32
+Your total was: $18.16
+
+We thought you might be interested in buying: Sneakers.
+
+Thank you,
+Tom
+", result);
+ }
+
+ #endregion
+
+ #region Argument Checking
+
+ ///
+ /// An exception should be thrown if the format string is null.
+ ///
+ [TestMethod]
+ [ExpectedException(typeof(ArgumentNullException))]
+ public void TestCtor_NullFormat_ThrowsException()
+ {
+ string format = null;
+ new Formatter(format);
+ }
+
+ ///
+ /// If we try to replace a placeholder that we do not have a lookup key for,
+ /// an exception should be thrown.
+ ///
+ [TestMethod]
+ [ExpectedException(typeof(KeyNotFoundException))]
+ public void TestFormat_MissingKey_ThrowsException()
+ {
+ Formatter formatter = new Formatter("{{unknown}}");
+ IDictionary lookup = new Dictionary();
+ formatter.Format(lookup);
+ }
+
+ ///
+ /// A format exception should be thrown if there is not a matching closing if tag.
+ ///
+ [TestMethod]
+ [ExpectedException(typeof(FormatException))]
+ public void TestFormat_MissingClosingIfTag_ThrowsException()
+ {
+ new Formatter("{{#if Bob}}Hello");
+ }
+
+ ///
+ /// A format exception should be thrown if the matching closing tag is wrong.
+ ///
+ [TestMethod]
+ [ExpectedException(typeof(FormatException))]
+ public void TestFormat_WrongClosingIfTag_ThrowsException()
+ {
+ new Formatter("{{#with this}}{{#if Bob}}Hello{{/with}}{{/if}}");
+ }
+
+ #endregion
+
+ ///
+ /// If we specify a right alignment, the output should be aligned to the right.
+ ///
+ [TestMethod]
+ public void TestFormatter_WithRightAlignment_AlignsToRight()
+ {
+ string format = "{{Name,10}}";
+ var instance = new
+ {
+ Name = "Bob"
+ };
+ PropertyDictionary dictionary = new PropertyDictionary(instance);
+ string result = Formatter.Format(format, dictionary);
+ Assert.AreEqual(" Bob", result, "The text was not aligned.");
+ }
+
+ ///
+ /// If we specify a left alignment, the output should be aligned to the left.
+ ///
+ [TestMethod]
+ public void TestFormatter_WithLeftAlignment_AlignsToLeft()
+ {
+ string format = "{{Name,-10}}";
+ var instance = new
+ {
+ Name = "Bob"
+ };
+ PropertyDictionary dictionary = new PropertyDictionary(instance);
+ string result = Formatter.Format(null, format, dictionary);
+ Assert.AreEqual("Bob ", result, "The text was not aligned.");
+ }
+
+ ///
+ /// If we try to format an empty string, an empty string should be returned.
+ ///
+ [TestMethod]
+ public void TestFormatter_EmptyFormat_ReturnsEmpty()
+ {
+ Formatter formatter = new Formatter(String.Empty);
+ Dictionary lookup = new Dictionary();
+ string result = formatter.Format(lookup);
+ Assert.AreEqual(String.Empty, result, "The result should have been empty.");
+ }
+
+ ///
+ /// If our format string is just a placeholder, than just the replacement value should be returned.
+ ///
+ [TestMethod]
+ public void TestFormatter_FormatIsSinglePlaceholder_ReturnsReplaced()
+ {
+ Formatter formatter = new Formatter("{{name}}");
+ Dictionary lookup = new Dictionary()
+ {
+ { "name", "test" }
+ };
+ string result = formatter.Format(lookup);
+ Assert.AreEqual("test", result, "The result was wrong.");
+ }
+
+ ///
+ /// We should be able to put just about anything inside of a placeholder, but it will
+ /// not be treated like a placeholder.
+ ///
+ [TestMethod]
+ public void TestFormatter_PlaceholderContainsSpecialCharacters_ReturnsUnreplaced()
+ {
+ Formatter formatter = new Formatter("{{ \\_@#$%^ }1233 abc}}");
+ Dictionary lookup = new Dictionary()
+ {
+ { " \\_@#$%^ }1233 abc", "test" }
+ };
+ string result = formatter.Format(lookup);
+ Assert.AreEqual("{{ \\_@#$%^ }1233 abc}}", result, "The result was wrong.");
+ }
+
+ ///
+ /// If a lookup value is null, it should be replaced with an empty string.
+ ///
+ [TestMethod]
+ public void TestFormatter_NullValue_ReplacesWithBlank()
+ {
+ Formatter formatter = new Formatter("These quotes should be empty '{{name}}'.");
+ Dictionary lookup = new Dictionary()
+ {
+ { "name", null }
+ };
+ string result = formatter.Format(lookup);
+ Assert.AreEqual("These quotes should be empty ''.", result, "The result was wrong.");
+ }
+
+ ///
+ /// If a replacement value contains a placeholder, it should NOT be evaluated.
+ ///
+ [TestMethod]
+ public void TestFormatter_ReplacementContainsPlaceholder_IgnoresPlaceholder()
+ {
+ Formatter formatter = new Formatter("The length of {{name}} is {{length}}.");
+ Dictionary lookup = new Dictionary()
+ {
+ { "name", "Bob" },
+ { "length", "{{name}}" }
+ };
+ string result = formatter.Format(lookup);
+ Assert.AreEqual("The length of Bob is {{name}}.", result, "The result was wrong.");
+ }
+
+ ///
+ /// If we pass null to as the format provider to the Format function,
+ /// the current culture is used.
+ ///
+ [TestMethod]
+ public void TestFormatter_NullFormatter_UsesCurrentCulture()
+ {
+ string format = "{0:C}";
+ Formatter formatter = new Formatter("{" + format + "}");
+ string result = formatter.Format((IFormatProvider)null, new Dictionary() { { "0", 28.30m } });
+ string expected = String.Format(CultureInfo.CurrentCulture, format, 28.30m);
+ Assert.AreEqual(expected, result, "The wrong format provider was used.");
+ }
+
+ ///
+ /// If we put a tag on a line by itself, it shouldn't result in any whitespace.
+ ///
+ [TestMethod]
+ public void TestFormatter_TagOnLineByItself_NoNewlineGenerated()
+ {
+ const string format = @"Hello
+{{#if Name}}
+{{Name}}
+{{/if}}
+Goodbye
+";
+ var data = new { Name = "George" };
+ Formatter formatter = new Formatter(format);
+ string result = formatter.Format(data);
+ const string expected = @"Hello
+George
+Goodbye
+";
+ Assert.AreEqual(expected, result);
+ }
+
+ ///
+ /// If a key is not found at the current level, it is looked for at the parent level.
+ ///
+ [TestMethod]
+ public void TestFormatter_NameAtHigherScope_Finds()
+ {
+ const string format = "{{#with Child}}{{TopLevel}} and {{ChildLevel}}{{/with}}";
+ Formatter formatter = new Formatter(format);
+ var data = new
+ {
+ TopLevel = "Parent",
+ Child = new { ChildLevel = "Child" },
+ };
+ string result = formatter.Format(data);
+ Assert.AreEqual("Parent and Child", result);
+ }
+
+ ///
+ /// Null values are considered false by if statements.
+ ///
+ [TestMethod]
+ public void TestFormatter_ConditionOnNull_ConsideredFalse()
+ {
+ const string format = "{{#if this}}Bad{{#else}}Good{{/if}}";
+ Formatter formatter = new Formatter(format);
+ string result = formatter.Format(null);
+ Assert.AreEqual("Good", result);
+ }
+
+ ///
+ /// Empty collections are considered false by if statements.
+ ///
+ [TestMethod]
+ public void TestFormatter_ConditionOnEmptyCollection_ConsideredFalse()
+ {
+ const string format = "{{#if this}}Bad{{#else}}Good{{/if}}";
+ Formatter formatter = new Formatter(format);
+ string result = formatter.Format(new object[0]);
+ Assert.AreEqual("Good", result);
+ }
+
+ ///
+ /// Non-empty collections are considered true by if statements.
+ ///
+ [TestMethod]
+ public void TestFormatter_ConditionOnNonEmptyCollection_ConsideredTrue()
+ {
+ const string format = "{{#if this}}Good{{#else}}Bad{{/if}}";
+ Formatter formatter = new Formatter(format);
+ string result = formatter.Format(new object[1]);
+ Assert.AreEqual("Good", result);
+ }
+
+ ///
+ /// Null-char is considered false by if statements.
+ ///
+ [TestMethod]
+ public void TestFormatter_ConditionOnNullChar_ConsideredFalse()
+ {
+ const string format = "{{#if this}}Bad{{#else}}Good{{/if}}";
+ Formatter formatter = new Formatter(format);
+ string result = formatter.Format('\0');
+ Assert.AreEqual("Good", result);
+ }
+
+ ///
+ /// Zero is considered false by if statements.
+ ///
+ [TestMethod]
+ public void TestFormatter_ConditionOnZero_ConsideredFalse()
+ {
+ const string format = "{{#if this}}Bad{{#else}}Good{{/if}}";
+ Formatter formatter = new Formatter(format);
+ int? value = 0;
+ string result = formatter.Format(value);
+ Assert.AreEqual("Good", result);
+ }
+
+ ///
+ /// Everything else is considered true by if statements.
+ ///
+ [TestMethod]
+ public void TestFormatter_ConditionOnDateTime_ConsideredTrue()
+ {
+ const string format = "{{#if this}}Good{{#else}}Bad{{/if}}";
+ Formatter formatter = new Formatter(format);
+ string result = formatter.Format(DateTime.Now);
+ Assert.AreEqual("Good", result);
+ }
+
+ ///
+ /// Instead of requiring deeply nested "with" statements, members
+ /// can be separated by dots.
+ ///
+ [TestMethod]
+ public void TestFormatter_NestedMembers_SearchesMembers()
+ {
+ const string format = "{{Customer.Name}}";
+ Formatter formatter = new Formatter(format);
+ var data = new { Customer = new { Name = "Bob" } };
+ string result = formatter.Format(data);
+ Assert.AreEqual("Bob", result);
+ }
+
+ ///
+ /// Keys should cause newlines to be respected, since they are considered content.
+ ///
+ [TestMethod]
+ public void TestFormatter_KeyBetweenTags_RespectsTrailingNewline()
+ {
+ string format = "{{#each this}}{{this}} {{/each}}" + Environment.NewLine;
+ Formatter formatter = new Formatter(format);
+ string result = formatter.Format("Hello");
+ Assert.AreEqual("H e l l o " + Environment.NewLine, result);
+ }
+
+ ///
+ /// If someone tries to loop on a non-enumerable, it should do nothing.
+ ///
+ [TestMethod]
+ public void TestFormatter_EachOnNonEnumerable_PrintsNothing()
+ {
+ const string format = "{{#each this}}Bad{{/each}}";
+ Formatter formatter = new Formatter(format);
+ string result = formatter.Format(123);
+ Assert.AreEqual(String.Empty, result);
+ }
+
+ ///
+ /// If a tag header is on the same line as it's footer, the new-line should not be removed.
+ ///
+ [TestMethod]
+ public void TestFormatter_InlineTags_RespectNewLine()
+ {
+ const string format = @"{{#if this}}{{/if}}
+";
+ Formatter formatter = new Formatter(format);
+ string result = formatter.Format(true);
+ Assert.AreEqual(Environment.NewLine, result);
+ }
+
+ ///
+ /// If a tag header is on the same line as it's footer, the new-line should not be removed.
+ ///
+ [TestMethod]
+ public void TestFormatter_TagFooterFollowedByTagHeader_RemovesNewLine()
+ {
+ const string format = @"{{#if this}}
+{{/if}}{{#if this}}
+Hello{{/if}}";
+ Formatter formatter = new Formatter(format);
+ string result = formatter.Format(true);
+ Assert.AreEqual("Hello", result);
+ }
+ }
+}
diff --git a/mustache-sharp.test/Properties/AssemblyInfo.cs b/mustache-sharp.test/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..0c78dd7
--- /dev/null
+++ b/mustache-sharp.test/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("mustache-sharp.test")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("mustache-sharp.test")]
+[assembly: AssemblyCopyright("Copyright © 2013")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("9975f293-f972-4751-9c8c-e25b17c0c8bc")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/mustache-sharp.test/PropertyDictionaryTester.cs b/mustache-sharp.test/PropertyDictionaryTester.cs
new file mode 100644
index 0000000..811505a
--- /dev/null
+++ b/mustache-sharp.test/PropertyDictionaryTester.cs
@@ -0,0 +1,484 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace mustache.test
+{
+ ///
+ /// Tests the PropertyDictionary class.
+ ///
+ [TestClass]
+ public class PropertyDictionaryTester
+ {
+ #region Real World Example
+
+ ///
+ /// The purpose of the PropertyDictionary class is to allow an object to be inspected,
+ /// as if it were a dictionary. This means we can get and set properties by their names.
+ ///
+ [TestMethod]
+ public void TestPropertyDictionary_AccessPropertiesViaIndexer()
+ {
+ var person = new
+ {
+ Name = "Bob",
+ Age = 23,
+ Birthday = new DateTime(2012, 03, 12)
+ };
+ PropertyDictionary wrapper = new PropertyDictionary(person);
+
+ Assert.AreEqual(3, wrapper.Count, "The wrong number of properties were created.");
+
+ Assert.IsTrue(wrapper.ContainsKey("Name"));
+ Assert.IsTrue(wrapper.ContainsKey("Age"));
+ Assert.IsTrue(wrapper.ContainsKey("Birthday"));
+
+ Assert.AreEqual(person.Name, wrapper["Name"], "The name was not wrapped.");
+ Assert.AreEqual(person.Age, wrapper["Age"], "The age was not wrapped.");
+ Assert.AreEqual(person.Birthday, wrapper["Birthday"], "The birthday was not wrapped.");
+ }
+
+ #endregion
+
+ #region Ctor & Instance & IsReadOnly
+
+ ///
+ /// If we try to wrap null, an exception should be thrown.
+ ///
+ [TestMethod]
+ public void TestCtor_NullInstance_ThrowsException()
+ {
+ PropertyDictionary dictionary = new PropertyDictionary(null);
+ Assert.AreEqual(0, dictionary.Count);
+ }
+
+ ///
+ /// We should be able to access the underlying object.
+ ///
+ [TestMethod]
+ public void TestCtor_SetsInstance()
+ {
+ object instance = new object();
+ PropertyDictionary dictionary = new PropertyDictionary(instance);
+ Assert.AreSame(instance, dictionary.Instance, "The instance was not set.");
+ ICollection> collection = dictionary;
+ Assert.IsTrue(collection.IsReadOnly, "The collection should not have been read-only.");
+ }
+
+ #endregion
+
+ #region Add
+
+ ///
+ /// Since the dictionary is a simple wrapper around an object, we cannot add new properties.
+ ///
+ [TestMethod]
+ [ExpectedException(typeof(NotSupportedException))]
+ public void TestAdd_IDictionary_ThrowsException()
+ {
+ IDictionary dictionary = new PropertyDictionary(new object());
+ dictionary.Add("Name", "Bob");
+ }
+
+ ///
+ /// Since the dictionary is a simple wrapper around an object, we cannot add new properties.
+ ///
+ [TestMethod]
+ [ExpectedException(typeof(NotSupportedException))]
+ public void TestAdd_ICollection_ThrowsException()
+ {
+ ICollection> collection = new PropertyDictionary(new object());
+ collection.Add(new KeyValuePair("Name", "Bob"));
+ }
+
+ #endregion
+
+ #region ContainsKey
+
+ ///
+ /// If the wrapped object has a property, the key should be found.
+ ///
+ [TestMethod]
+ public void TestContainsKey_PropertyExists_ReturnsTrue()
+ {
+ var person = new
+ {
+ Name = "Bob",
+ };
+ PropertyDictionary dictionary = new PropertyDictionary(person);
+ bool result = dictionary.ContainsKey("Name");
+ Assert.IsTrue(result, "The property name was not found.");
+ }
+
+ ///
+ /// If the wrapped object does not have a property, the key should not be found.
+ ///
+ [TestMethod]
+ public void TestContainsKey_PropertyMissing_ReturnsFalse()
+ {
+ var person = new { };
+ PropertyDictionary dictionary = new PropertyDictionary(person);
+ bool result = dictionary.ContainsKey("Name");
+ Assert.IsFalse(result, "The property name was found.");
+ }
+
+ private class BaseType
+ {
+ public string Inherited { get; set; }
+ }
+
+ private class DerivedType : BaseType
+ {
+ public string Local { get; set; }
+ }
+
+ ///
+ /// We should be able to see properties defined in the base type.
+ ///
+ [TestMethod]
+ public void TestContainsKey_PropertyInherited_ReturnsTrue()
+ {
+ BaseType b = new DerivedType();
+ PropertyDictionary dictionary = new PropertyDictionary(b);
+ bool result = dictionary.ContainsKey("Inherited");
+ Assert.IsTrue(result, "The property name was not found.");
+ }
+
+ private class PrivateType
+ {
+ private string Hidden { get; set; }
+ }
+
+ ///
+ /// We should not be able to see private properties.
+ ///
+ [TestMethod]
+ public void TestContainsKey_PropertyPrivate_ReturnsFalse()
+ {
+ PrivateType t = new PrivateType();
+ PropertyDictionary dictionary = new PropertyDictionary(t);
+ bool result = dictionary.ContainsKey("Hidden");
+ Assert.IsFalse(result, "The property name was found.");
+ }
+
+ private class StaticType
+ {
+ public static string Static { get; set; }
+ }
+
+ ///
+ /// We should not be able to see static properties.
+ ///
+ [TestMethod]
+ public void TestContainsKey_PropertyStatic_ReturnsFalse()
+ {
+ StaticType t = new StaticType();
+ PropertyDictionary dictionary = new PropertyDictionary(t);
+ bool result = dictionary.ContainsKey("Static");
+ Assert.IsFalse(result, "The property name was found.");
+ }
+
+ #endregion
+
+ #region Keys
+
+ ///
+ /// Keys should return the name of all of the property names in the object.
+ ///
+ [TestMethod]
+ public void TestKeys_GetsAllPropertyNames()
+ {
+ var person = new
+ {
+ Name = "Bob",
+ Age = 23
+ };
+ PropertyDictionary dictionary = new PropertyDictionary(person);
+ ICollection keys = dictionary.Keys;
+ Assert.AreEqual(2, keys.Count, "The wrong number of keys were returned.");
+ Assert.IsTrue(keys.Contains("Name"), "The Name property was not found.");
+ Assert.IsTrue(keys.Contains("Age"), "The Age property was not found.");
+ }
+
+ #endregion
+
+ #region Remove
+
+ ///
+ /// Since a property dictionary is just a wrapper around an object, we cannot remove properties from it.
+ ///
+ [TestMethod]
+ [ExpectedException(typeof(NotSupportedException))]
+ public void TestRemove_IDictionary_ThrowsException()
+ {
+ object instance = new object();
+ IDictionary dictionary = new PropertyDictionary(instance);
+ dictionary.Remove("Name");
+ }
+
+ ///
+ /// Since a property dictionary is just a wrapper around an object, we cannot remove properties from it.
+ ///
+ [TestMethod]
+ [ExpectedException(typeof(NotSupportedException))]
+ public void TestRemove_ICollection_ThrowsException()
+ {
+ object instance = new object();
+ ICollection> collection = new PropertyDictionary(instance);
+ collection.Remove(new KeyValuePair("Name", "Whatever"));
+ }
+
+ #endregion
+
+ #region TryGetValue
+
+ ///
+ /// If we try to get the value for a property that doesn't exist, false should returned and object set to null.
+ ///
+ [TestMethod]
+ public void TestTryGetValue_NoSuchProperty_ReturnsFalse()
+ {
+ var instance = new { };
+ PropertyDictionary dictionary = new PropertyDictionary(instance);
+ object value;
+ bool result = dictionary.TryGetValue("Name", out value);
+ Assert.IsFalse(result, "The property should not have been found.");
+ Assert.IsNull(value, "The value should have been null.");
+ }
+
+ ///
+ /// If we try to get the value for a property that doesn't exist, false should returned and object set to null.
+ ///
+ [TestMethod]
+ public void TestTryGetValue_PropertyExists_ReturnsTrue()
+ {
+ var instance = new
+ {
+ Name = "Test"
+ };
+ PropertyDictionary dictionary = new PropertyDictionary(instance);
+ object value;
+ bool result = dictionary.TryGetValue("Name", out value);
+ Assert.IsTrue(result, "The property should have been found.");
+ Assert.AreEqual(instance.Name, value, "The value should have equaled the wrapped property value.");
+ }
+
+ #endregion
+
+ #region Values
+
+ ///
+ /// We should be able to get the value of all of the properties.
+ ///
+ [TestMethod]
+ public void TestValues_GetsValues()
+ {
+ var instance = new
+ {
+ Name = "Bob",
+ Age = 23
+ };
+ PropertyDictionary dictionary = new PropertyDictionary(instance);
+ ICollection