diff --git a/Deployment/NuGet.exe b/Deployment/NuGet.exe index 8d13fd8..9f8781d 100644 Binary files a/Deployment/NuGet.exe and b/Deployment/NuGet.exe differ diff --git a/Deployment/publish-mustache-sharp.bat b/Deployment/publish-mustache-sharp.bat index 6221d79..d7385f2 100644 --- a/Deployment/publish-mustache-sharp.bat +++ b/Deployment/publish-mustache-sharp.bat @@ -1,4 +1,4 @@ -msbuild ../mustache-sharp.sln /p:Configuration=Release +"C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" ../mustache-sharp.sln /p:Configuration=Release nuget pack ../mustache-sharp/mustache-sharp.csproj -Properties Configuration=Release nuget push *.nupkg del *.nupkg \ No newline at end of file diff --git a/README.md b/README.md index bdba1f1..c1b42e1 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Introducing [handlebars.js](http://handlebarsjs.com/)... If you've needed to gen Most of the lines in the previous example will never appear in the final output. This allows you to use **mustache#** to write templates for normal text, not just HTML/XML. ## Placeholders -The placeholders can be any valid identifier. These map to the property names in your classes. +The placeholders can be any valid identifier. These map to the property names in your classes (or `Dictionary` keys). ### Formatting Placeholders Each format item takes the following form and consists of the following components: @@ -247,3 +247,44 @@ Here's an example of a tag that will join the items of a collection: writer.Write(joined); } } + +## HTML Support +**mustache#** was not originally designed to exclusively generate HTML. However, it is by far the most common use of **mustache#**. For that reason, there is a separate `HtmlFormatCompiler` class that will automatically configure the code to work with HTML documents. Particularly, this class will eliminate most newlines and escape any special HTML characters that might appear within the substituted values. + +If you really need to embed HTML values, you can wrap placeholders in triple quotes rather than double quotes. + + HtmlFormatCompiler compiler = new HtmlFormatCompiler(); + const string format = @"{{escaped}} and {{{unescaped}}}"; + Generator generator = compiler.Compile(format); + string result = generator.Render(new + { + escaped = "Awesome", + unescaped = "sweet" + }); + // Generates <b>Awesome</b> and sweet + +## License +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/TraceAndTestImpact.testsettings b/TraceAndTestImpact.testsettings deleted file mode 100644 index 99dbff3..0000000 --- a/TraceAndTestImpact.testsettings +++ /dev/null @@ -1,21 +0,0 @@ - - - These are test settings for Trace and Test Impact. - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/UNLICENSE.txt b/UNLICENSE.txt new file mode 100644 index 0000000..68a49da --- /dev/null +++ b/UNLICENSE.txt @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/mustache-sharp.test/FormatCompilerTester.cs b/mustache-sharp.test/FormatCompilerTester.cs index 3797b14..35ba44b 100644 --- a/mustache-sharp.test/FormatCompilerTester.cs +++ b/mustache-sharp.test/FormatCompilerTester.cs @@ -452,6 +452,82 @@ Content"; Assert.AreEqual(String.Empty, context[0].TagName, "The top-most context had the wrong tag type."); } + /// + /// If a key refers to a public field, its value should be substituted in the output. + /// + [TestMethod] + public void TestGenerate_KeyRefersToPublicField_SubstitutesValue() + { + FormatCompiler compiler = new FormatCompiler(); + const string format = @"Hello, {{Field}}!!!"; + Generator generator = compiler.Compile(format); + ClassWithPublicField instance = new ClassWithPublicField() { Field = "Bob" }; + string result = generator.Render(instance); + Assert.AreEqual("Hello, Bob!!!", result, "The wrong text was generated."); + } + + public class ClassWithPublicField + { + public string Field; + } + + /// + /// If a derived class replaces a property/field in the base class (via new) + /// it should be used, instead of causing an exception or using the base's + /// property/field. + /// + [TestMethod] + public void TestGenerate_NewPropertyInDerivedClass_UsesDerivedProperty() + { + FormatCompiler compiler = new FormatCompiler(); + const string format = @"Hello, {{Value}}!!!"; + Generator generator = compiler.Compile(format); + DerivedClass instance = new DerivedClass() { Value = "Derived" }; + string result = generator.Render(instance); + Assert.AreEqual("Hello, Derived!!!", result, "The wrong text was generated."); + } + + public class BaseClass + { + public int Value { get; set; } + } + + public class DerivedClass : BaseClass + { + public DerivedClass() + { + base.Value = 1; + } + + public new string Value { get; set; } + } + + /// + /// If a derived class replaces a property/field in the base class (via new) + /// it should be used, instead of causing an exception or using the base's + /// property/field. + /// + [TestMethod] + public void TestGenerate_NewPropertyInGenericDerivedClass_UsesDerivedProperty() + { + FormatCompiler compiler = new FormatCompiler(); + const string format = @"Hello, {{Value}}!!!"; + Generator generator = compiler.Compile(format); + DerivedClass instance = new DerivedClass() { Value = "Derived" }; + string result = generator.Render(instance); + Assert.AreEqual("Hello, Derived!!!", result, "The wrong text was generated."); + } + + public class DerivedClass : BaseClass + { + public DerivedClass() + { + base.Value = 1; + } + + public new T Value { get; set; } + } + #endregion #region Comment @@ -716,6 +792,110 @@ Middle"; Assert.AreEqual("BeforeAfter", result, "The wrong text was generated."); } + /// + /// If the condition evaluates to false, the content of an if statement should not be printed. + /// + [TestMethod] + public void TestCompile_If_null_SkipsContent() + { + FormatCompiler parser = new FormatCompiler(); + const string format = "Before{{#if this}}Content{{/if}}After"; + Generator generator = parser.Compile(format); + string result = generator.Render(null); + Assert.AreEqual("BeforeAfter", result, "The wrong text was generated."); + } + + /// + /// If the condition evaluates to false, the content of an if statement should not be printed. + /// + [TestMethod] + public void TestCompile_If_DBNull_SkipsContent() + { + FormatCompiler parser = new FormatCompiler(); + const string format = "Before{{#if this}}Content{{/if}}After"; + Generator generator = parser.Compile(format); + string result = generator.Render(DBNull.Value); + Assert.AreEqual("BeforeAfter", result, "The wrong text was generated."); + } + + /// + /// If the condition evaluates to false, the content of an if statement should not be printed. + /// + [TestMethod] + public void TestCompile_If_EmptyIEnumerable_SkipsContent() + { + FormatCompiler parser = new FormatCompiler(); + const string format = "Before{{#if this}}Content{{/if}}After"; + Generator generator = parser.Compile(format); + string result = generator.Render(Enumerable.Empty()); + Assert.AreEqual("BeforeAfter", result, "The wrong text was generated."); + } + + /// + /// If the condition evaluates to false, the content of an if statement should not be printed. + /// + [TestMethod] + public void TestCompile_If_NullChar_SkipsContent() + { + FormatCompiler parser = new FormatCompiler(); + const string format = "Before{{#if this}}Content{{/if}}After"; + Generator generator = parser.Compile(format); + string result = generator.Render('\0'); + Assert.AreEqual("BeforeAfter", result, "The wrong text was generated."); + } + + /// + /// If the condition evaluates to false, the content of an if statement should not be printed. + /// + [TestMethod] + public void TestCompile_If_ZeroInt_SkipsContent() + { + FormatCompiler parser = new FormatCompiler(); + const string format = "Before{{#if this}}Content{{/if}}After"; + Generator generator = parser.Compile(format); + string result = generator.Render(0); + Assert.AreEqual("BeforeAfter", result, "The wrong text was generated."); + } + + /// + /// If the condition evaluates to false, the content of an if statement should not be printed. + /// + [TestMethod] + public void TestCompile_If_ZeroFloat_SkipsContent() + { + FormatCompiler parser = new FormatCompiler(); + const string format = "Before{{#if this}}Content{{/if}}After"; + Generator generator = parser.Compile(format); + string result = generator.Render(0f); + Assert.AreEqual("BeforeAfter", result, "The wrong text was generated."); + } + + /// + /// If the condition evaluates to false, the content of an if statement should not be printed. + /// + [TestMethod] + public void TestCompile_If_ZeroDouble_SkipsContent() + { + FormatCompiler parser = new FormatCompiler(); + const string format = "Before{{#if this}}Content{{/if}}After"; + Generator generator = parser.Compile(format); + string result = generator.Render(0.0); + Assert.AreEqual("BeforeAfter", result, "The wrong text was generated."); + } + + /// + /// If the condition evaluates to false, the content of an if statement should not be printed. + /// + [TestMethod] + public void TestCompile_If_ZeroDecimal_SkipsContent() + { + FormatCompiler parser = new FormatCompiler(); + const string format = "Before{{#if this}}Content{{/if}}After"; + Generator generator = parser.Compile(format); + string result = generator.Render(0m); + Assert.AreEqual("BeforeAfter", result, "The wrong text was generated."); + } + /// /// If the condition evaluates to false, the content of an if statement should not be printed. /// @@ -844,7 +1024,7 @@ Content{{/if}}"; /// If the a header follows a footer, it shouldn't generate a new line. /// [TestMethod] - public void TestCompile_IfNewLineContentNewLineEndIfIfNewLineContenNewLineEndIf_PrintsContent() + public void TestCompile_IfNewLineContentNewLineEndIfIfNewLineContentNewLineEndIf_PrintsContent() { FormatCompiler parser = new FormatCompiler(); const string format = @"{{#if this}} @@ -1423,43 +1603,128 @@ Odd #endregion + #region New Line Management + + /// + /// If the compiler is configured to ignore new lines, + /// they should not be removed from the output. + /// + [TestMethod] + public void TestCompile_PreserveNewLines() + { + FormatCompiler compiler = new FormatCompiler(); + compiler.RemoveNewLines = false; + const string format = @"Hello + "; + + const string expected = @"Hello + "; + Generator generator = compiler.Compile(format); + string result = generator.Render(null); + Assert.AreEqual(expected, result, "The wrong text was generated."); + } + + #endregion + + #region Strings + + /// + /// We will use a string variable to determine whether or not to print out a line. + /// + [TestMethod] + public void TestCompile_StringArgument_PassedToTag() + { + FormatCompiler compiler = new FormatCompiler(); + const string format = @"{{#if 'hello'}}Hello{{/if}}"; + Generator generator = compiler.Compile(format); + string actual = generator.Render(null); + string expected = "Hello"; + Assert.AreEqual(expected, actual, "The string was not passed to the formatter."); + } + + /// + /// We will use a string variable to determine whether or not to print out a line. + /// + [TestMethod] + public void TestCompile_EmptyStringArgument_PassedToTag() + { + FormatCompiler compiler = new FormatCompiler(); + const string format = @"{{#if ''}}Hello{{/if}}"; + Generator generator = compiler.Compile(format); + string actual = generator.Render(null); + string expected = ""; + Assert.AreEqual(expected, actual, "The string was not passed to the formatter."); + } + + #endregion + + #region Numbers + + /// + /// We will use a number variable to determine whether or not to print out a line. + /// + [TestMethod] + public void TestCompile_NumberArgument_PassedToTag() + { + FormatCompiler compiler = new FormatCompiler(); + const string format = @"{{#if 4}}Hello{{/if}}"; + Generator generator = compiler.Compile(format); + string actual = generator.Render(null); + string expected = "Hello"; + Assert.AreEqual(expected, actual, "The number was not passed to the formatter."); + } + + /// + /// We will use a string variable to determine whether or not to print out a line. + /// + [TestMethod] + public void TestCompile_ZeroNumberArgument_PassedToTag() + { + FormatCompiler compiler = new FormatCompiler(); + const string format = @"{{#if 00.0000}}Hello{{/if}}"; + Generator generator = compiler.Compile(format); + string actual = generator.Render(null); + string expected = ""; + Assert.AreEqual(expected, actual, "The number was not passed to the formatter."); + } + + #endregion #region ValueIntemplateTests - [TestMethod] - public void TestCompile_CanUseStringValueInEquals() - { + [TestMethod] + public void TestCompile_CanUseStringValueInEquals() { FormatCompiler compiler = new FormatCompiler(); const string format = @"{{#eq Value _Yesterday}}Yes!{{/eq}}"; Generator generator = compiler.Compile(format); - - string actual = generator.Render(new {Value = "Yesterday"}); + + string actual = generator.Render(new { Value = "Yesterday" }); string expected = "Yes!"; Assert.AreEqual(expected, actual, "Value field didn't work"); } - [TestMethod] - public void TestCompile_CanUseNumericValueInEquals() { - FormatCompiler compiler = new FormatCompiler(); - const string format = @"{{#eq Value _123.3231}}Yes!{{/eq}}"; - Generator generator = compiler.Compile(format); - - - string actual = generator.Render(new { Value = "123.3231" }); - string expected = "Yes!"; - Assert.AreEqual(expected, actual, "Value field didn't work"); - } - - [TestMethod] - public void TestCompile_NonEqualNumericValue() { - FormatCompiler compiler = new FormatCompiler(); - const string format = @"{{#eq Value _123.3232}}Yes!{{/eq}}"; - Generator generator = compiler.Compile(format); + [TestMethod] + public void TestCompile_CanUseNumericValueInEquals() { + FormatCompiler compiler = new FormatCompiler(); + const string format = @"{{#eq Value _123.3231}}Yes!{{/eq}}"; + Generator generator = compiler.Compile(format); - string actual = generator.Render(new { Value = "123.3231" }); - string expected = ""; - Assert.AreEqual(expected, actual, "Value field didn't work"); - } + string actual = generator.Render(new { Value = "123.3231" }); + string expected = "Yes!"; + Assert.AreEqual(expected, actual, "Value field didn't work"); + } + + [TestMethod] + public void TestCompile_NonEqualNumericValue() { + FormatCompiler compiler = new FormatCompiler(); + const string format = @"{{#eq Value _123.3232}}Yes!{{/eq}}"; + Generator generator = compiler.Compile(format); + + + string actual = generator.Render(new { Value = "123.3231" }); + string expected = ""; + Assert.AreEqual(expected, actual, "Value field didn't work"); + } #endregion @@ -1468,21 +1733,24 @@ Odd [TestMethod] public void TestCompile_UrlEncode() { FormatCompiler compiler = new FormatCompiler(); + compiler.RegisterTag(new UrlEncodeTagDefinition(), true); const string format = @"{{#urlencode}}https://google.com{{/urlencode}}"; Generator generator = compiler.Compile(format); - string actual = generator.Render(new {}); + string actual = generator.Render(new { }); string expected = "https%3a%2f%2fgoogle.com"; Assert.AreEqual(expected, actual, "Value field didn't work"); } [TestMethod] - public void TestCompile_UrlEncodeParam() { + public void TestCompile_UrlEncodeVariableText() { FormatCompiler compiler = new FormatCompiler(); + compiler.RegisterTag(new UrlEncodeTagDefinition(), true); + const string format = @"{{#urlencode}}{{url}}{{/urlencode}}"; Generator generator = compiler.Compile(format); - string actual = generator.Render(new {url="https://google.com" }); + string actual = generator.Render(new { url = "https://google.com" }); string expected = "https%3a%2f%2fgoogle.com"; Assert.AreEqual(expected, actual, "Value field didn't work"); } @@ -1490,6 +1758,7 @@ Odd [TestMethod] public void TestCompile_UrlDecode() { FormatCompiler compiler = new FormatCompiler(); + const string format = @"{{#urldecode}}https%3a%2f%2fgoogle.com{{/urldecode}}"; Generator generator = compiler.Compile(format); @@ -1501,6 +1770,5 @@ Odd #endregion - } } diff --git a/mustache-sharp.test/HtmlFormatCompilerTester.cs b/mustache-sharp.test/HtmlFormatCompilerTester.cs new file mode 100644 index 0000000..60e5974 --- /dev/null +++ b/mustache-sharp.test/HtmlFormatCompilerTester.cs @@ -0,0 +1,32 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Mustache.Test +{ + [TestClass] + public class HtmlFormatCompilerTester + { + [TestMethod] + public void ShouldEscapeValueContainingHTMLCharacters() + { + HtmlFormatCompiler compiler = new HtmlFormatCompiler(); + var generator = compiler.Compile("Hello, {{Name}}!!!"); + string html = generator.Render(new + { + Name = "John \"The Man\" Standford" + }); + Assert.AreEqual("Hello, John "The Man" Standford!!!", html); + } + + [TestMethod] + public void ShouldIgnoreHTMLCharactersInsideTripleCurlyBraces() + { + HtmlFormatCompiler compiler = new HtmlFormatCompiler(); + var generator = compiler.Compile("Hello, {{{Name}}}!!!"); + string html = generator.Render(new + { + Name = "John \"The Man\" Standford" + }); + Assert.AreEqual("Hello, John \"The Man\" Standford!!!", html); + } + } +} diff --git a/mustache-sharp.test/Properties/AssemblyInfo.cs b/mustache-sharp.test/Properties/AssemblyInfo.cs index 4466edd..3bbd9f9 100644 --- a/mustache-sharp.test/Properties/AssemblyInfo.cs +++ b/mustache-sharp.test/Properties/AssemblyInfo.cs @@ -1,35 +1,15 @@ 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: AssemblyCompany("Truncon")] [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("0.2.2.0")] -[assembly: AssemblyFileVersion("0.2.2.0")] +[assembly: AssemblyVersion("0.0.0.0")] +[assembly: AssemblyFileVersion("0.0.0.0")] diff --git a/mustache-sharp.test/UpcastDictionaryTester.cs b/mustache-sharp.test/UpcastDictionaryTester.cs new file mode 100644 index 0000000..2b8b50d --- /dev/null +++ b/mustache-sharp.test/UpcastDictionaryTester.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Mustache.Test +{ + [TestClass] + public class UpcastDictionaryTester + { + [TestMethod] + public void ShouldReturnNullForNull() + { + IDictionary result = UpcastDictionary.Create(null); + Assert.IsNull(result, "Null should be returned for null."); + } + + [TestMethod] + public void ShouldReturnArgumentIfIDictionary_string_object() + { + object source = new Dictionary(); + IDictionary result = UpcastDictionary.Create(source); + Assert.AreSame(source, result, "The up-cast wrapper should not be applied if already a IDictionary."); + } + + [TestMethod] + public void ShouldReturnNullIfNotGenericType() + { + object source = String.Empty; + IDictionary result = UpcastDictionary.Create(source); + Assert.IsNull(result, "Null should be returned for non-generic types."); + } + + [TestMethod] + public void ShouldReturnNullIfWrongNumberOfGenericArguments() + { + object source = new List(); + IDictionary result = UpcastDictionary.Create(source); + Assert.IsNull(result, "Null should be returned for generic types with the wrong number of type arguments."); + } + + [TestMethod] + public void ShouldReturnNullIfFirstGenericTypeArgumentIsNotAString() + { + object source = new Dictionary(); + IDictionary result = UpcastDictionary.Create(source); + Assert.IsNull(result, "Null should be returned if the first generic type argument is not a string."); + } + + [TestMethod] + public void ShouldReturnNullIfNotDictionaryType() + { + object source = (Converter)(s => (object)s); + IDictionary result = UpcastDictionary.Create(source); + Assert.IsNull(result, "Null should be returned for non-dictionary types."); + } + + [TestMethod] + public void ShouldReturnUpcastWrapperForDictionary_string_TValue() + { + object source = new Dictionary(); + IDictionary result = UpcastDictionary.Create(source); + Assert.IsInstanceOfType(result, typeof(UpcastDictionary), "The source was not wrapped."); + } + + [TestMethod] + public void ShouldFindKeyIfInWrappedDictionary() + { + object source = new Dictionary() { { "Name", "Bob" } }; + IDictionary result = UpcastDictionary.Create(source); + bool containsKey = result.ContainsKey("Name"); + Assert.IsTrue(containsKey, "The key Name should have been found."); + } + + [TestMethod] + public void ShouldNotFindKeyIfNotInWrappedDictionary() + { + object source = new Dictionary() { { "Name", "Bob" } }; + IDictionary result = UpcastDictionary.Create(source); + bool containsKey = result.ContainsKey("Age"); + Assert.IsFalse(containsKey, "The key Age should not have been found."); + } + + [TestMethod] + public void ShouldFindKeysInWrappedDictionary() + { + var source = new Dictionary() { { "Name", "Bob" }, { "Age", "100" } }; + IDictionary result = UpcastDictionary.Create(source); + ICollection sourceKeys = source.Keys; + ICollection wrappedKeys = result.Keys.ToArray(); + CollectionAssert.AreEquivalent(sourceKeys, wrappedKeys, "The same keys should have been found in both collections."); + } + + [TestMethod] + public void ShouldFindKeyIfInWrappedDictionary_TryGetValue() + { + var source = new Dictionary() { { "Name", "Bob" } }; + IDictionary result = UpcastDictionary.Create(source); + object value; + bool found = result.TryGetValue("Name", out value); + Assert.IsTrue(found, "The key should have been found."); + Assert.AreSame(source["Name"], value, "The value in the underlying dictionary should have been returned."); + } + + [TestMethod] + public void ShouldNotFindKeyIfNotInWrappedDictionary_TryGetValue() + { + var source = new Dictionary() { { "Age", 100 } }; + IDictionary result = UpcastDictionary.Create(source); + object value; + bool found = result.TryGetValue("Name", out value); + Assert.IsFalse(found, "The key should not have been found."); + Assert.IsNull(value, "The value should be null even if the actual type is a struct."); + + } + + [TestMethod] + public void ShouldReturnValuesAsObjects() + { + var source = new Dictionary() { { "Age", 100 }, { "Weight", 500 } }; + IDictionary result = UpcastDictionary.Create(source); + ICollection sourceValues = source.Values; + ICollection wrappedValues = result.Values.ToArray(); + CollectionAssert.AreEquivalent(sourceValues, wrappedValues, "The underlying values were not returned."); + } + + [TestMethod] + public void ShouldFindKeyIfInWrappedDictionary_Indexer() + { + var source = new Dictionary() { { "Name", "Bob" } }; + IDictionary result = UpcastDictionary.Create(source); + object value = result["Name"]; + Assert.AreSame(source["Name"], value, "The value in the underlying dictionary should have been returned."); + } + + [TestMethod] + [ExpectedException(typeof(KeyNotFoundException))] + public void ShouldNotFindKeyIfNotInWrappedDictionary_Indexer() + { + var source = new Dictionary() { { "Age", 100 } }; + IDictionary result = UpcastDictionary.Create(source); + object value = result["Name"]; + } + + [TestMethod] + public void ShouldNotFindPairIfValueWrongType() + { + var source = new Dictionary() { { "Age", 100 } }; + IDictionary result = UpcastDictionary.Create(source); + bool contains = result.Contains(new KeyValuePair("Age", "Blah")); + Assert.IsFalse(contains, "The pair should not have been found."); + } + + [TestMethod] + public void ShouldFindPairInWrappedDictionary() + { + var source = new Dictionary() { { "Age", 100 } }; + IDictionary result = UpcastDictionary.Create(source); + bool contains = result.Contains(new KeyValuePair("Age", 100)); + Assert.IsTrue(contains, "The pair should have been found."); + } + + [TestMethod] + public void ShouldCopyPairsToArray() + { + var source = new Dictionary() { { "Age", 100 }, { "Weight", 45 } }; + IDictionary result = UpcastDictionary.Create(source); + var array = new KeyValuePair[2]; + result.CopyTo(array, 0); + var expected = new KeyValuePair[] + { + new KeyValuePair("Age", 100), + new KeyValuePair("Weight", 45) + }; + CollectionAssert.AreEqual(expected, array, "The pairs were not copied."); + } + + [TestMethod] + public void ShouldGetCount() + { + var source = new Dictionary() { { "Age", 100 }, { "Weight", 45 } }; + IDictionary result = UpcastDictionary.Create(source); + Assert.AreEqual(source.Count, result.Count, "The source and Upcast dictionary should have the same count."); + } + + [TestMethod] + public void ShouldGetEnumerator() + { + var source = new Dictionary() { { "Age", 100 }, { "Weight", 45 } }; + IDictionary result = UpcastDictionary.Create(source); + IEnumerator> enumerator = result.GetEnumerator(); + var values = new List>(); + while (enumerator.MoveNext()) + { + values.Add(enumerator.Current); + } + var expected = new KeyValuePair[] + { + new KeyValuePair("Age", 100), + new KeyValuePair("Weight", 45) + }; + CollectionAssert.AreEqual(expected, values, "The enumerator did not return the correct pairs."); + } + + /// + /// Newtonsoft's JSON.NET has an object called JObject. This is a concrete class + /// that inherits from IDictionary<string, JToken>. The UpcastDictionary + /// should be able to handle this type. + /// + [TestMethod] + public void ShouldHandleConcreteClassInheritingFromDictionary() + { + var dictionary = new ConcreteDictionary() { { "Name", "Bob" } }; + var result = UpcastDictionary.Create(dictionary); + Assert.AreEqual(dictionary["Name"], result["Name"]); + } + + public class ConcreteDictionary : Dictionary + { + } + } +} diff --git a/mustache-sharp.test/mustache-sharp.test.csproj b/mustache-sharp.test/mustache-sharp.test.csproj index 0457468..0d2e9e3 100644 --- a/mustache-sharp.test/mustache-sharp.test.csproj +++ b/mustache-sharp.test/mustache-sharp.test.csproj @@ -33,6 +33,12 @@ prompt 4 + + true + + + mustache-sharp.test.snk + @@ -44,14 +50,19 @@ + + - {D71B378F-A4BA-4263-A4F0-07A49A0C528D} + {d71b378f-a4ba-4263-a4f0-07a49a0c528d} mustache-sharp + + +